commit 2565d02a1d0c3d206070deceac25f1fa3d693c17 Author: Marko Oldenburg Date: Thu Mar 17 06:35:30 2022 +0100 first init diff --git a/73_PRESENCE.pm b/73_PRESENCE.pm new file mode 100755 index 0000000..6698fb9 --- /dev/null +++ b/73_PRESENCE.pm @@ -0,0 +1,2121 @@ +# $Id: 73_PRESENCE.pm 20782 2019-12-19 10:51:06Z markusbloch $ +############################################################################## +# +# 73_PRESENCE.pm +# Checks for the presence of a mobile phone or tablet by network ping or bluetooth detection. +# It reports the presence of this device as state. +# +# Copyright by Markus Bloch +# e-mail: Notausstieg0309@googlemail.com +# +# 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 . +# +############################################################################## + +package main; + +use strict; +use warnings; +use Blocking; +use Time::HiRes qw(gettimeofday usleep sleep); +use DevIo; + +sub +PRESENCE_Initialize($) +{ + my ($hash) = @_; + + # Provider + $hash->{ReadFn} = "PRESENCE_Read"; + $hash->{ReadyFn} = "PRESENCE_Ready"; + $hash->{SetFn} = "PRESENCE_Set"; + $hash->{DefFn} = "PRESENCE_Define"; + $hash->{NotifyFn} = "PRESENCE_Notify"; + $hash->{UndefFn} = "PRESENCE_Undef"; + $hash->{AttrFn} = "PRESENCE_Attr"; + $hash->{AttrList} = "do_not_notify:0,1 ". + "disable:0,1 ". + "disabledForIntervals ". + "fritzboxCheckSpeed:0,1 ". + "pingCount:1,2,3,4,5,6,7,8,9,10 ". + "bluetoothHciDevice ". + "absenceThreshold ". + "presenceThreshold ". + "absenceTimeout ". + "presenceTimeout ". + "powerCmd ". + "retryInterval ". + "retryCount ". + $readingFnAttributes; + + $hash->{AttrRenameMap} = { "ping_count" => "pingCount", + "bluetooth_hci_device" => "bluetoothHciDevice", + "fritzbox_speed" => "fritzboxCheckSpeed" + }; + +} + +##################################### +sub +PRESENCE_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t]+", $def); + my $dev; + my $username = getlogin || getpwuid($<) || "[unknown]"; + my $name = $hash->{NAME}; + + $hash->{NOTIFYDEV} = "global"; + + if(defined($a[2]) and defined($a[3])) + { + if($a[2] eq "local-bluetooth") + { + unless($a[3] =~ /^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*$/) + { + my $msg = "given address is not a bluetooth hardware address"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg + } + + $hash->{MODE} = "local-bluetooth"; + $hash->{ADDRESS} = $a[3]; + $hash->{INTERVAL_NORMAL} = (defined($a[4]) ? $a[4] : 30); + $hash->{INTERVAL_PRESENT} = (defined($a[5]) ? $a[5] : $hash->{INTERVAL_NORMAL}); + } + elsif($a[2] eq "fritzbox") + { + unless(-X "/usr/bin/ctlmgr_ctl") + { + my $msg = "this is not a fritzbox or you running FHEM with the AVM Beta Image. Please use the FHEM FritzBox Image from fhem.de"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + unless($username eq "root") + { + my $msg = "FHEM is not running under root (currently $username) This check can only performed with root access to the FritzBox"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + $hash->{MODE} = "fritzbox"; + $hash->{ADDRESS} = $a[3]; + $hash->{INTERVAL_NORMAL} = (defined($a[4]) ? $a[4] : 30); + $hash->{INTERVAL_PRESENT} = (defined($a[5]) ? $a[5] : $hash->{INTERVAL_NORMAL}); + } + elsif($a[2] eq "lan-ping") + { + if(-X "/usr/bin/ctlmgr_ctl" and not $username eq "root") + { + my $msg = "FHEM is not running under root (currently $username) This check can only performed with root access to the FritzBox"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + $hash->{MODE} = "lan-ping"; + $hash->{ADDRESS} = $a[3]; + $hash->{INTERVAL_NORMAL} = (defined($a[4]) ? $a[4] : 30); + $hash->{INTERVAL_PRESENT} = (defined($a[5]) ? $a[5] : $hash->{INTERVAL_NORMAL}); + } + elsif($a[2] =~ /(shellscript|function)/) + { + if($def =~ /(\S+) \w+ (\S+) ["']{0,1}(.+?)['"]{0,1}\s*(\d*)\s*(\d*)$/s) + { + $hash->{MODE} = $2; + $hash->{helper}{call} = $3; + $hash->{INTERVAL_NORMAL} = ($4 ne "" ? $4 : 30); + $hash->{INTERVAL_PRESENT} = ($5 ne "" ? $5 : $hash->{INTERVAL_NORMAL}); + + delete($hash->{helper}{ADDRESS}); + + if($hash->{helper}{call} =~ /\|/) + { + my $msg = "The command contains a pipe ( | ) symbol, which is not allowed."; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + if($hash->{MODE} eq "function" and not $hash->{helper}{call} =~ /^\{.+\}$/) + { + my $msg = "The function call must be encapsulated by brackets ( {...} )."; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + } + } + elsif($a[2] eq "lan-bluetooth") + { + unless($a[3] =~ /^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*$/) + { + my $msg = "given address is not a bluetooth hardware address"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg + } + + DevIo_CloseDev($hash); + + $hash->{MODE} = "lan-bluetooth"; + $hash->{ADDRESS} = $a[3]; + $hash->{INTERVAL_NORMAL} = (defined($a[5]) ? $a[5] : 30); + $hash->{INTERVAL_PRESENT} = (defined($a[6]) ? $a[6] : $hash->{INTERVAL_NORMAL}); + + $dev = $a[4]; + $dev .= ":5222" if($dev !~ m/:/ && $dev ne "none" && $dev !~ m/\@/); + + $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" if($init_done); + + 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, function or event"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg + } + } + else + { + my $msg = "wrong syntax for define statement: define PRESENCE [ [ ] ]"; + Log 2, "PRESENCE ($name) - $msg"; + return $msg; + } + + my $timeout = $hash->{INTERVAL_NORMAL}; + my $presence_timeout = $hash->{INTERVAL_PRESENT}; + + if(defined($timeout) and not $timeout =~ /^\d+$/) + { + my $msg = "check-interval must be a number"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + if(defined($timeout) and not $timeout > 0) + { + my $msg = "check-interval must be greater than zero"; + Log 2, "PRESENCE ($name) -".$msg; + return $msg; + } + + if(defined($presence_timeout) and not $presence_timeout =~ /^\d+$/) + { + my $msg = "presence-check-interval must be a number"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + if(defined($presence_timeout) and not $presence_timeout > 0) + { + my $msg = "presence-check-interval must be greater than zero"; + Log 2, "PRESENCE ($name) - ".$msg; + return $msg; + } + + delete($hash->{helper}{cachednr}); + + readingsSingleUpdate($hash,"model",$hash->{MODE},0); + + return undef; +} + +##################################### +sub +PRESENCE_Undef($$) +{ + my ($hash, $arg) = @_; + + RemoveInternalTimer($hash); + + if(defined($hash->{helper}{RUNNING_PID})) + { + BlockingKill($hash->{helper}{RUNNING_PID}); + } + + DevIo_CloseDev($hash); + return undef; +} + +##################################### +sub +PRESENCE_Notify($$) +{ + my ($hash,$dev) = @_; + + 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(grep(m/^(?:INITIALIZED|REREADCFG)$/, @{$events})) + { + $hash->{helper}{ABSENT_COUNT} = int(ReadingsVal($name, ".absenceThresholdCounter", 0)); + $hash->{helper}{PRESENT_COUNT} = int(ReadingsVal($name, ".presenceThresholdCounter", 0)); + } + + if($hash->{MODE} =~ /(lan-ping|local-bluetooth|fritzbox|shellscript|function)/) + { + delete $hash->{helper}{RUNNING_PID} if(defined($hash->{helper}{RUNNING_PID})); + RemoveInternalTimer($hash); + InternalTimer(gettimeofday(), "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED}); + return undef; + } + elsif($hash->{MODE} eq "lan-bluetooth") + { + delete($hash->{NEXT_OPEN}) if(exists($hash->{NEXT_OPEN})); + 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($@) +{ + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + + return "No Argument given" if(!defined($a[1])); + + my $usage = "Unknown argument ".$a[1].", choose one of statusRequest"; + + my $powerCmd = AttrVal($name, "powerCmd", undef); + $usage .= " power" if(defined($powerCmd)); + + $usage .= " overrideInterval" if($hash->{MODE} !~ /^event|lan-bluetooth$/); + $usage .= " clearOverride:noArg" if($hash->{INTERVAL_OVERRIDED}); + $usage .= " inactive:noArg active:noArg"; + + if($a[1] eq "statusRequest") + { + if($hash->{MODE} ne "lan-bluetooth") + { + Log3 $name, 5, "PRESENCE ($name) - starting local scan"; + + return PRESENCE_StartLocalScan($hash, 1); + } + else + { + if(exists($hash->{FD})) + { + DevIo_SimpleWrite($hash, "now\n", 2); + } + else + { + return "PRESENCE Definition \"$name\" is not connected to ".$hash->{DeviceName}; + } + } + } + elsif(defined($powerCmd) && $a[1] eq "power") + { + my %specials= ( + '%NAME' => $name, + '%ADDRESS' => (defined($hash->{ADDRESS}) ? $hash->{ADDRESS} : ""), + '%ARGUMENT' => (defined($a[2]) ? $a[2] : "") + ); + + $powerCmd = EvalSpecials($powerCmd, %specials); + + Log3 $name, 5, "PRESENCE ($name) - executing powerCmd: $powerCmd"; + my $return = AnalyzeCommandChain(undef, $powerCmd); + + if($return) + { + Log3 $name, 3, "PRESENCE ($name) - executed powerCmd failed: ".$return; + readingsSingleUpdate($hash, "powerCmd", "failed",1); + return "executed powerCmd failed: ".$return; + } + else + { + readingsSingleUpdate($hash, "powerCmd", "executed",1); + } + } + elsif($a[1] eq "overrideInterval") + { + if($a[2] and $a[2] =~ /^\d+$/) + { + Log3 $name, 3, "PRESENCE ($name) - overriding regular check intervals to ".$a[2]." seconds"; + $hash->{INTERVAL_OVERRIDED} = $a[2]; + } + else + { + return "invalid override interval given (must be a positive integer)" + } + } + elsif($a[1] eq "clearOverride") + { + delete($hash->{INTERVAL_OVERRIDED}); + } + elsif($a[1] eq "inactive") + { + if(defined($hash->{FD})) + { + DevIo_SimpleWrite($hash, "stop\n", 2); + } + RemoveInternalTimer($hash); + $hash->{helper}{DISABLED} = 1; + readingsSingleUpdate($hash, "state", "disabled",1); + } + elsif($a[1] eq "active") + { + readingsSingleUpdate($hash, "state", "defined",0) if(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 1); + + if(defined($hash->{DeviceName})) + { + $hash->{helper}{DISABLED} = 0; + + if(defined($hash->{FD})) + { + PRESENCE_DoInit($hash) if(exists($hash->{helper}{DISABLED})); + } + else + { + DevIo_OpenDev($hash, 0, "PRESENCE_DoInit"); + } + } + else + { + $hash->{helper}{DISABLED} = 0; + PRESENCE_StartLocalScan($hash); + } + + } + else + { + return $usage; + } + + return undef; +} + + +########################## +sub +PRESENCE_Attr(@) +{ + my @a = @_; + my $hash = $defs{$a[1]}; + + if($a[0] eq "set" && $a[2] eq "disable") + { + if($a[3] eq "0") + { + readingsSingleUpdate($hash, "state", "defined",0) if(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 1); + + if(defined($hash->{DeviceName})) + { + $hash->{helper}{DISABLED} = 0; + + if(defined($hash->{FD})) + { + PRESENCE_DoInit($hash) if(exists($hash->{helper}{DISABLED})); + + } + else + { + DevIo_OpenDev($hash, 0, "PRESENCE_DoInit"); + } + } + else + { + $hash->{helper}{DISABLED} = 0; + PRESENCE_StartLocalScan($hash); + } + + $hash->{helper}{DISABLED} = 0; + } + elsif($a[3] eq "1") + { + if(defined($hash->{FD})) + { + DevIo_SimpleWrite($hash, "stop\n", 2); + } + + RemoveInternalTimer($hash); + + $hash->{helper}{DISABLED} = 1; + readingsSingleUpdate($hash, "state", "disabled",1); + } + } + elsif($a[0] eq "del" && $a[2] eq "disable") + { + readingsSingleUpdate($hash, "state", "defined",0) if(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 1); + + if(defined($hash->{DeviceName})) + { + $hash->{helper}{DISABLED} = 0; + + if(defined($hash->{FD})) + { + PRESENCE_DoInit($hash) if(exists($hash->{helper}{DISABLED})); + } + else + { + DevIo_OpenDev($hash, 0, "PRESENCE_DoInit"); + } + } + else + { + $hash->{helper}{DISABLED} = 0; + PRESENCE_StartLocalScan($hash); + } + } + elsif($a[0] eq "set" and $a[2] eq "powerOnFn") + { + my $powerOnFn = $a[3]; + + $powerOnFn =~ s/^\s+//; + $powerOnFn =~ s/\s+$//; + + if($powerOnFn eq "") + { + return "powerOnFn contains no value"; + } + } + elsif($a[0] eq "set" and $a[2] eq "absenceThreshold") + { + 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") + { + 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."; + } + } + elsif($a[0] eq "set" and $a[2] =~ /^(retryInterval|retryCount)$/) + { + return $a[2]." must be a valid positive integer number" if($a[3] !~ /^\d+$/); + return $a[2]." is not applicable for mode '".$hash->{MODE}."'" if($hash->{MODE} =~ /^(event|lan-bluetooth)$/); + } + + return undef; +} + + +##################################### +# Receives an event and creates several readings for event triggering +sub +PRESENCE_Read($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $buf = DevIo_SimpleRead($hash); + return "" if(!defined($buf)); + + chomp $buf; + + readingsBeginUpdate($hash); + + for my $line (split /^/, $buf) + { + Log3 $name, 5, "PRESENCE ($name) - received data: $line"; + + if($line =~ /^absence|absent/) + { + if(!$hash->{helper}{DISABLED} and $hash->{helper}{CURRENT_TIMEOUT} eq "present" and $hash->{INTERVAL_NORMAL} != $hash->{INTERVAL_PRESENT}) + { + $hash->{helper}{CURRENT_TIMEOUT} = "normal"; + Log3 $name, 4 , "PRESENCE ($name) - changing to normal timeout every ".$hash->{INTERVAL_NORMAL}." seconds"; + DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{INTERVAL_NORMAL}."\n", 2); + } + + unless($hash->{helper}{DISABLED}) + { + PRESENCE_ProcessState($hash, "absent"); + + if($line=~ /^[^;]+;(.+)$/) + { + PRESENCE_ProcessAddonData($hash, $1); + } + } + } + elsif($line =~ /present;(.+?)$/) + { + if(!$hash->{helper}{DISABLED} and $hash->{helper}{CURRENT_TIMEOUT} eq "normal" and $hash->{INTERVAL_NORMAL} != $hash->{INTERVAL_PRESENT}) + { + $hash->{helper}{CURRENT_TIMEOUT} = "present"; + Log3 $name, 4 , "PRESENCE ($name) - changing to present timeout every ".$hash->{INTERVAL_PRESENT}." seconds"; + DevIo_SimpleWrite($hash, $hash->{ADDRESS}."|".$hash->{INTERVAL_PRESENT}."\n", 2); + } + + unless($hash->{helper}{DISABLED}) + { + PRESENCE_ProcessState($hash, "present"); + my $data = $1; + + if($data =~ /\S=\S/) + { + PRESENCE_ProcessAddonData($hash, $data); + } + else + { + if($1 =~ /^(.*);(.+)$/) + { + readingsBulkUpdate($hash, "room", $2); + readingsBulkUpdate($hash, "device_name", $1); + } + else + { + readingsBulkUpdate($hash, "device_name", $1); + } + } + } + } + elsif($line eq "command accepted") + { + readingsBulkUpdate($hash, "command_accepted", "yes"); + } + elsif($line eq "command rejected") + { + readingsBulkUpdate($hash, "command_accepted", "no"); + } + elsif($line =~ /socket_closed;(?:room='?)?(.+?)'?$/) + { + Log3 $name, 3, "PRESENCE ($name) - collectord lost connection to room $1"; + } + elsif($line =~ /socket_reconnected;(?:room='?)?(.+?)'?$/) + { + Log3 $name , 3, "PRESENCE ($name) - collectord reconnected to room $1"; + } + elsif($line =~ /error;(?:room='?)?(.+?)'?$/) + { + Log3 $name, 3, "PRESENCE ($name) - room $1 cannot execute hcitool to check device"; + } + elsif($line =~ /error$/) + { + Log3 $name, 3, "PRESENCE ($name) - presenced cannot execute hcitool to check device "; + } + } + + readingsEndUpdate($hash, 1); +} + +##################################### +sub +PRESENCE_Ready($) +{ + my ($hash) = @_; + + 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) = @_; + my $name = $hash->{NAME}; + my $mode = $hash->{MODE}; + + $local = 0 unless(defined($local)); + + if(not (exists($hash->{ADDRESS}) or exists($hash->{helper}{call}))) + { + return undef; + } + + unless(exists($hash->{helper}{RUNNING_PID})) + { + $hash->{STATE} = "active" if($hash->{STATE} eq "???" or $hash->{STATE} eq "defined"); + + if($local == 0) + { + Log3 $name, 5, "PRESENCE ($name) - stopping timer"; + RemoveInternalTimer($hash); + } + + if($mode eq "local-bluetooth") + { + Log3 $name, 5, "PRESENCE ($name) - starting blocking call for mode local-bluetooth"; + $hash->{helper}{RUNNING_PID} = BlockingCall("PRESENCE_DoLocalBluetoothScan", $name."|".$hash->{ADDRESS}."|".$local."|".AttrVal($name, "bluetoothHciDevice", ""), "PRESENCE_ProcessLocalScan", 60, "PRESENCE_ProcessAbortedScan", $hash); + } + elsif($mode eq "lan-ping") + { + Log3 $name, 5, "PRESENCE ($name) - starting blocking call for mode lan-ping"; + $hash->{helper}{RUNNING_PID} = BlockingCall("PRESENCE_DoLocalPingScan", $name."|".$hash->{ADDRESS}."|".$local."|".AttrVal($name, "pingCount", "4"), "PRESENCE_ProcessLocalScan", 60, "PRESENCE_ProcessAbortedScan", $hash); + } + elsif($mode eq "fritzbox") + { + Log3 $name, 5, "PRESENCE ($name) - starting blocking call for mode fritzbox"; + $hash->{helper}{RUNNING_PID} = BlockingCall("PRESENCE_DoLocalFritzBoxScan", $name."|".$hash->{ADDRESS}."|".$local."|".AttrVal($name, "fritzboxCheckSpeed", "0"), "PRESENCE_ProcessLocalScan", 60, "PRESENCE_ProcessAbortedScan", $hash); + } + elsif($mode eq "shellscript") + { + Log3 $name, 5, "PRESENCE ($name) - starting blocking call for mode shellscript"; + $hash->{helper}{RUNNING_PID} = BlockingCall("PRESENCE_DoLocalShellScriptScan", $name."|".$hash->{helper}{call}."|".$local, "PRESENCE_ProcessLocalScan", 60, "PRESENCE_ProcessAbortedScan", $hash); + } + elsif($mode eq "function") + { + Log3 $name, 5, "PRESENCE ($name) - starting blocking call for mode function"; + $hash->{helper}{RUNNING_PID} = BlockingCall("PRESENCE_DoLocalFunctionScan", $name."|".$hash->{helper}{call}."|".$local, "PRESENCE_ProcessLocalScan", 60, "PRESENCE_ProcessAbortedScan", $hash); + } + + if(!$hash->{helper}{RUNNING_PID} and $mode =~ /^local-bluetooth|lan-ping|fritzbox|shellscript|function$/) + { + delete($hash->{helper}{RUNNING_PID}); + + my $seconds = (ReadingsVal($name, "state", "absent") eq "present" ? $hash->{INTERVAL_PRESENT} : $hash->{INTERVAL_NORMAL}); + + Log3 $hash->{NAME}, 4, "PRESENCE ($name) - fork failed, rescheduling next check in $seconds seconds"; + + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$seconds, "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED}); + } + + return undef; + } + else + { + Log3 $hash->{NAME}, 4, "PRESENCE ($name) - another check is currently running. skipping check"; + + if($local == 0) + { + my $seconds = (ReadingsVal($name, "state", "absent") eq "present" ? $hash->{INTERVAL_PRESENT} : $hash->{INTERVAL_NORMAL}); + + 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}); + } + + return "another check is currently running"; + } +} + +##################################### +sub PRESENCE_DoLocalPingScan($) +{ + + my ($string) = @_; + my ($name, $device, $local, $count) = split("\\|", $string); + + Log3 $name, 5, "PRESENCE ($name) - starting ping scan: $string"; + + my $retcode; + my $return; + my $temp; + + $SIG{CHLD} = 'IGNORE'; + + if($^O =~ m/(Win|cygwin)/) + { + $temp = qx(ping -n $count -4 $device); + + if(defined($temp) and $temp ne "") + { + chomp $temp; + Log3 $name, 5, "PRESENCE ($name) - ping command returned with output:\n$temp"; + $return = "$name|$local|".($temp =~ /TTL=\d+/ ? "present" : "absent"); + } + else + { + $return = "$name|$local|error|Could not execute ping command: \"ping -n $count -4 $device\""; + } + } + elsif($^O =~ m/solaris/) + { + $temp = qx(ping $device 4); + + if(defined($temp) and $temp ne "") + { + chomp $temp; + Log3 $name, 5, "PRESENCE ($name) - ping command returned with output:\n$temp"; + $return = "$name|$local|".($temp =~ /is alive/ ? "present" : "absent"); + } + else + { + $return = "$name|$local|error|Could not execute ping command: \"ping -n $count -4 $device\""; + } + + } + else + { + $temp = qx(ping -c $count $device 2>&1); + + if(defined($temp) and $temp ne "") + { + chomp $temp; + Log3 $name, 5, "PRESENCE ($name) - ping command returned with output:\n$temp"; + $return = "$name|$local|".(($temp =~ /\d+ [Bb]ytes (from|von)/ and not $temp =~ /[Uu]nreachable/) ? "present" : "absent"); + } + else + { + $return = "$name|$local|error|Could not execute ping command: \"ping -c $count $device\""; + } + } + + return $return; +} + +##################################### +sub PRESENCE_ExecuteFritzBoxCMD($$) +{ + + my ($name, $cmd) = @_; + my $status; + my $wait; + + while(-e "/var/tmp/fhem-PRESENCE-cmd-lock.tmp" and (stat("/var/tmp/fhem-PRESENCE-cmd-lock.tmp"))[9] > (gettimeofday() - 2)) + { + $wait = int(rand(4))+2; + Log3 $name, 5, "PRESENCE ($name) - ctlmgr_ctl is locked. waiting $wait seconds..."; + $wait = 1000000*$wait; + usleep $wait; + } + + unlink("/var/tmp/fhem-PRESENCE-cmd-lock.tmp") if(-e "/var/tmp/fhem-PRESENCE-cmd-lock.tmp"); + + qx(touch /var/tmp/fhem-PRESENCE-cmd-lock.tmp); + + Log3 $name, 5, "PRESENCE ($name) - executing ctlmgr_ctl: $cmd"; + $status = qx($cmd); + usleep 200000; + unlink("/var/tmp/fhem-PRESENCE-cmd-lock.tmp") if(-e "/var/tmp/fhem-PRESENCE-cmd-lock.tmp"); + + return $status; +} + +##################################### +sub PRESENCE_DoLocalFritzBoxScan($) +{ + my ($string) = @_; + my ($name, $device, $local, $speedcheck) = split("\\|", $string); + + Log3 $name, 5, "PRESENCE ($name) - starting fritzbox scan: $string"; + + my $number = 0; + my $status = 0; + my $speed; + + $SIG{CHLD} = 'IGNORE'; + + my $check_command = ($device =~ /^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*$/ ? "mac" : "name"); + + $device = uc $device if($device =~ /^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*$/); + + if(defined($defs{$name}{helper}{cachednr})) + { + $number = $defs{$name}{helper}{cachednr}; + + Log3 $name, 5, "PRESENCE ($name) - try checking $name as device $device with cached number $number"; + my $cached_name = ""; + + $cached_name = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/$check_command"); + + chomp $cached_name; + + # only use the cached $number if it has still the correct device name + if($cached_name eq $device) + { + Log3 $name, 5, "PRESENCE ($name) - checking state with cached number ($number)"; + $status = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/active"); + chomp $status; + + if($status ne "0" and $speedcheck eq "1") + { + $speed = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/speed"); + chomp $speed; + Log3 $name, 5, "PRESENCE ($name) - speed check returned: $speed"; + $speed = undef if($speed eq "0"); + } + + Log3 $name, 5, "PRESENCE ($name) - ctlmgr_ctl (cached: $number) returned: $status"; + + if(not $status =~ /^\s*\d+\s*$/) + { + return "$name|$local|error|could not execute ctlmgr_ctl (cached)"; + } + + return ($status == 0 ? "$name|$local|absent|$number" : "$name|$local|present|$number").($speedcheck == 1 and defined($speed) ? "|$speed" :""); + } + else + { + Log3 $name, 5, "PRESENCE ($name) - cached device ($cached_name) does not match expected device ($device). perform a full scan"; + } + } + + my $max = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice/count"); + + chomp $max; + + Log3 $name, 5, "PRESENCE ($name) - ctlmgr_ctl (getting device count) returned: $max"; + + if(not $max =~ /^\s*\d+\s*$/) + { + return "$name|$local|error|could not execute ctlmgr_ctl"; + } + + my $net_device; + + $number = 0; + + while($number <= $max) + { + $net_device = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/$check_command"); + + chomp $net_device; + + Log3 $name, 5, "PRESENCE ($name) - checking device number $number ($net_device)"; + + if($net_device eq $device) + { + $status = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/active"); + chomp $status; + + if($status ne "0" and $speedcheck eq "1") + { + $speed = PRESENCE_ExecuteFritzBoxCMD($name, "/usr/bin/ctlmgr_ctl r landevice settings/landevice$number/speed"); + chomp $speed; + Log3 $name, 5, "PRESENCE ($name) - speed check returned: $speed"; + $speed = undef if($speed eq "0"); + } + + Log3 $name, 5, "PRESENCE ($name) - state for device number $net_device is $status"; + + last; + } + + $number++; + } + + return ($status == 0 ? "$name|$local|absent" : "$name|$local|present").($number <= $max ? "|$number" : "|").($speedcheck == 1 and defined($speed) ? "|$speed" : ""); +} + +##################################### +sub PRESENCE_DoLocalBluetoothScan($) +{ + my ($string) = @_; + my ($name, $device, $local, $btdevice) = split("\\|", $string); + + my $devname; + my $return; + my $wait = 1; + my $ps; + my $psargs = "ax"; + + Log3 $name, 5, "PRESENCE ($name) - starting bluetooth scan: $string"; + + $SIG{CHLD} = 'IGNORE'; + + if(qx(ps --help 2>&1) =~ /BusyBox/g) + { + Log3 $name, 5, "PRESENCE ($name) - found busybox variant of ps command, using \"w\" as parameter"; + $psargs = "w"; + } + else + { + Log3 $name, 5, "PRESENCE ($name) - found standard variant of ps command, using \"ax\" as parameter"; + $psargs = "ax"; + } + + Log3 $name, 4, "PRESENCE ($name) - executing: which hcitool"; + my $hcitool = qx(which hcitool); + Log3 $name, 4, "PRESENCE ($name) - 'which hcitool' returns: $hcitool"; + chomp $hcitool; + + if(-x $hcitool) + { + my $options = ($btdevice ? "-i $btdevice" : ""); + + while($wait) + { # check if another hcitool process is running + $ps = qx(ps $psargs | grep hcitool | grep -v grep); + if(not $ps =~ /^\s*$/) + { + # sleep between 1 and 5 seconds and try again + Log3 $name, 5, "PRESENCE ($name) - another hcitool command is running. waiting..."; + sleep(rand(4)+1); + } + else + { + $wait = 0; + } + } + + Log3 $name, 5, "PRESENCE ($name) - executing: hcitool name $device"; + $devname = qx(hcitool $options name $device); + + chomp($devname); + Log3 $name, 4, "PRESENCE ($name) - hcitool returned: $devname"; + + if(not $devname =~ /^\s*$/) + { + $return = "$name|$local|present|$devname"; + } + else + { + $return = "$name|$local|absent"; + } + } + else + { + $return = "$name|$local|error|no hcitool binary found. Please check that the bluez-package is properly installed"; + } + + return $return; +} + +##################################### +sub PRESENCE_DoLocalShellScriptScan($) +{ + + my ($string) = @_; + my ($name, $call, $local) = split("\\|", $string); + + my $ret; + my $return; + + Log3 $name, 5, "PRESENCE ($name) - starting local shell script scan: $string"; + + $SIG{CHLD} = 'IGNORE'; + + $ret = qx($call 2>&1); + + if(defined($ret)) + { + chomp $ret; + Log3 $name, 5, "PRESENCE ($name) - script output: $ret"; + } + + if(not defined($ret)) + { + $return = "$name|$local|error|scriptcall doesn't return any output"; + } + elsif($ret eq "1") + { + $return = "$name|$local|present"; + } + elsif($ret eq "0") + { + $return = "$name|$local|absent"; + } + else + { + $ret =~ s/\n/<>/g; + + $return = "$name|$local|error|unexpected script output (expected 0 or 1): $ret"; + } + + return $return; +} + +##################################### +sub PRESENCE_DoLocalFunctionScan($) +{ + + my ($string) = @_; + my ($name, $call, $local) = split("\\|", $string); + + my $ret; + my $return; + + Log3 $name, 5, "PRESENCE ($name) - execute perl function: $string"; + + $SIG{CHLD} = 'IGNORE'; + + $ret = AnalyzeCommandChain(undef, $call); + + chomp $ret; + + Log3 $name, 5, "PRESENCE ($name) - function returned with: $ret"; + + if(not defined($ret)) + { + $return = "$name|$local|error|function call doesn't return any output"; + } + elsif($ret eq "1") + { + $return = "$name|$local|present"; + } + elsif($ret eq "0") + { + $return = "$name|$local|absent"; + } + else + { + $ret =~ s/\n/<>/g; + + $return = "$name|$local|error|unexpected function output (expected 0 or 1): $ret"; + } + + return $return; +} + +##################################### +sub PRESENCE_ProcessLocalScan($) +{ + my ($string) = @_; + + return unless(defined($string)); + + my @a = split("\\|",$string); + my $hash = $defs{$a[0]}; + + my $local = $a[1]; + my $name = $hash->{NAME}; + + Log3 $hash->{NAME}, 5, "PRESENCE ($name) - blocking scan result: $string"; + + delete($hash->{helper}{RUNNING_PID}); + + if($hash->{helper}{DISABLED}) + { + Log3 $hash->{NAME}, 5, "PRESENCE ($name) - don't process the scan result, as $name is disabled"; + return; + } + + if(defined($hash->{helper}{RETRY_COUNT})) + { + Log3 $hash->{NAME}, 2, "PRESENCE ($name) - check returned a valid result after ".$hash->{helper}{RETRY_COUNT}." unsuccesful ".($hash->{helper}{RETRY_COUNT} > 1 ? "retries" : "retry"); + delete($hash->{helper}{RETRY_COUNT}); + } + + if($hash->{MODE} eq "fritzbox" and defined($a[3]) and $a[3] ne "") + { + $hash->{helper}{cachednr} = $a[3] if(($a[2] eq "present") || ($a[2] eq "absent")); + } + elsif($hash->{MODE} eq "fritzbox" and defined($hash->{helper}{cachednr})) + { + delete($hash->{helper}{cachednr}); + } + + readingsBeginUpdate($hash); + + PRESENCE_ProcessState($hash, $a[2]) unless($hash->{helper}{DISABLED}); + + if($a[2] eq "present") + { + readingsBulkUpdate($hash, "device_name", $a[3]) if($hash->{MODE} =~ /^(lan-bluetooth|local-bluetooth)$/ and defined($a[3])); + + readingsBulkUpdate($hash, "speed", $a[4]) if($hash->{MODE} eq "fritzbox" and defined($a[4])); + } + elsif($a[2] eq "absent") + { + readingsBulkUpdate($hash, "speed", $a[4]) if($hash->{MODE} eq "fritzbox" and defined($a[4])); + } + elsif($a[2] eq "error") + { + $a[3] =~ s/<>/\n/g; + + Log3 $hash->{NAME}, 2, "PRESENCE ($name) - error while processing check: ".$a[3]; + } + + readingsEndUpdate($hash, 1); + + #Schedule the next check within $timeout if it is a regular run + if($local eq "0") + { + my $seconds = (($a[2] eq "present") ? $hash->{INTERVAL_PRESENT} : $hash->{INTERVAL_NORMAL}); + + $seconds = $hash->{INTERVAL_OVERRIDED} if($hash->{INTERVAL_OVERRIDED}); + + Log3 $hash->{NAME}, 4, "PRESENCE ($name) - rescheduling next check in $seconds seconds"; + + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$seconds, "PRESENCE_StartLocalScan", $hash) unless($hash->{helper}{DISABLED}); + } +} + +##################################### +sub PRESENCE_ProcessAbortedScan($) +{ + my ($hash, $msg) = @_; + my $name = $hash->{NAME}; + delete($hash->{helper}{RUNNING_PID}); + RemoveInternalTimer($hash); + + my $retry_interval = AttrVal($name,"retryInterval",10); + + if(defined($hash->{helper}{RETRY_COUNT})) + { + if($hash->{helper}{RETRY_COUNT} >= AttrVal($name, "retryCount", 3)) + { + Log3 $hash->{NAME}, 2, "PRESENCE ($name) - device could not be checked after ".$hash->{helper}{RETRY_COUNT}." ".($hash->{helper}{RETRY_COUNT} > 1 ? "retries" : "retry"). " (resuming normal operation): $msg" if($hash->{helper}{RETRY_COUNT} == 3); + InternalTimer(gettimeofday()+$hash->{INTERVAL_NORMAL}, "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED}); + $hash->{helper}{RETRY_COUNT}++; + } + else + { + Log3 $hash->{NAME}, 2, "PRESENCE ($name) - device could not be checked after ".$hash->{helper}{RETRY_COUNT}." ".($hash->{helper}{RETRY_COUNT} > 1 ? "retries" : "retry")." (retrying in $retry_interval seconds): $msg"; + InternalTimer(gettimeofday()+$retry_interval, "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED}); + $hash->{helper}{RETRY_COUNT}++; + } + } + else + { + $hash->{helper}{RETRY_COUNT} = 1; + InternalTimer(gettimeofday()+$retry_interval, "PRESENCE_StartLocalScan", $hash, 0) unless($hash->{helper}{DISABLED}); + Log3 $hash->{NAME}, 2, "PRESENCE ($name) - device could not be checked (retrying in $retry_interval seconds): $msg" + } + + readingsSingleUpdate($hash, "state", "timeout",1); +} + +########################################################################################################################## +# +# 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->{INTERVAL_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($hash->{MODE} eq "event") + { + 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 + { + if(++$count >= $absenceThreshold) + { + readingsBulkUpdate($hash, ".presenceThresholdCounter", 0); + readingsBulkUpdate($hash, ".absenceThresholdCounter", ($count-1)); + readingsBulkUpdate($hash, "state", "absent"); + readingsBulkUpdate($hash, "presence", "absent"); + } + else + { + $hash->{helper}{ABSENT_COUNT} = $count; + + readingsBulkUpdate($hash, ".presenceThresholdCounter", 0); + readingsBulkUpdate($hash, ".absenceThresholdCounter", $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}); + } + elsif($state eq "present") + { + RemoveInternalTimer($hash, "PRESENCE_ThresholdTrigger"); + my $count = ($hash->{helper}{PRESENT_COUNT} ? $hash->{helper}{PRESENT_COUNT} : 0); + + if($hash->{MODE} eq "event") + { + 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 + { + if(++$count >= $presenceThreshold) + { + readingsBulkUpdate($hash, ".absenceThresholdCounter", 0); + readingsBulkUpdate($hash, ".presenceThresholdCounter", ($count-1)); + readingsBulkUpdate($hash, "state", "present"); + readingsBulkUpdate($hash, "presence", "present"); + + $hash->{helper}{CURRENT_STATE} = "present"; + } + else + { + $hash->{helper}{PRESENT_COUNT} = $count; + + readingsBulkUpdate($hash, ".absenceThresholdCounter", 0); + readingsBulkUpdate($hash, ".presenceThresholdCounter", $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"; + } + } + + delete($hash->{helper}{ABSENT_COUNT}); + } + else + { + readingsBulkUpdate($hash, "state", $state); + } +} + +##################################### +sub PRESENCE_ProcessAddonData($$) +{ + my ($hash, $data) = @_; + + my ($a, $h) = parseParams($data, ";"); + + foreach my $key (sort keys %{$h}) + { + readingsBulkUpdate($hash, $key, $h->{$key}) if(defined($h->{$key})); + } + + return undef; +} + +##################################### +sub PRESENCE_setNotfiyDev($) +{ + my ($hash) = @_; + + notifyRegexpChanged($hash,"(global|".$hash->{EVENT_PRESENT}."|".$hash->{EVENT_ABSENT}.")"); +} + +1; + +=pod +=item helper +=item summary provides presence detection checks +=item summary_DE stellt eine Anwesenheitserkennung zur Verfügung +=begin html + + +

PRESENCE

+
    + The PRESENCE module provides several possibilities to check the presence of mobile phones or similar mobile devices such as tablets. +

    + This module provides several operational modes to serve your needs. These are:

    +
      +
    • lan-ping - A presence check of a device via network ping in your LAN/WLAN.
    • +
    • fritzbox - A presence check by requesting the device state from the FritzBox internals (only available when running FHEM on a FritzBox!).
    • +
    • local-bluetooth - A presence check by searching directly for a given bluetooth device nearby.
    • +
    • function - A presence check by using your own perl function which returns a presence state.
    • +
    • shellscript - A presence check by using an self-written script or binary which returns a presence state.
    • +
    • event - A presence check by listening to FHEM events of other definitions.
    • +
    • lan-bluetooth - A presence check of a bluetooth device via LAN network by connecting to a presenced or collectord instance.
    • +
    +
    + Each mode can be optionally configured with a specific check interval and a present check interval.

    +
      +
    • check-interval - The interval in seconds between each presence check. Default value: 30 seconds
    • +
    • present-check-interval - The interval in seconds between each presence check in case the device is present. Otherwise the normal check-interval will be used.
    • +
    +

    + + Define

    +
      Mode: lan-ping

      + define <name> PRESENCE lan-ping <ip-address> [ <check-interval> [ <present-check-interval> ] ]
      +
      + Checks for a network device via PING requests and reports its presence state.

      + Example

      + define iPhone PRESENCE lan-ping 192.168.179.21
      +
      + Mode: fritzbox

      + define <name> PRESENCE fritzbox <device-name/mac-address> [ <check-interval> [ <present-check-interval> ] ]
      +
      + Checks for a network device by requesting the internal state on a FritzBox via ctlmgr_ctl. The device-name must be the same as shown in the network overview of the FritzBox or can be substituted by the MAC address with the format XX:XX:XX:XX:XX:XX

      + This check is only applicable when FHEM is running on a FritzBox! The detection of absence can take about 10-15 minutes!

      + Example

      + define iPhone PRESENCE fritzbox iPhone-6
      + define iPhone PRESENCE fritzbox 00:06:08:05:0D:00

      + Mode: local-bluetooth

      + define <name> PRESENCE local-bluetooth <bluetooth-address> [ <check-interval> [ <present-check-interval> ] ]
      +
      + Checks for a bluetooth device and reports its presence state. For this mode the shell command "hcitool" is required (provided with a bluez installation under Debian via APT), as well + as a functional bluetooth device directly attached to your machine.

      + Example

      + define iPhone PRESENCE local-bluetooth 0a:8d:4f:51:3c:8f

      + Mode: function

      + define <name> PRESENCE function {...} [ <check-interval> [ <present-check-interval> ] ]
      +
      + Checks for a presence state via perl-code. You can use a self-written perl function to obtain the presence state of a specific device (e.g. via SNMP check).

      + The function must return 0 (absent) or 1 (present). An example can be found in the FHEM-Wiki.

      + Example

      + define iPhone PRESENCE function {snmpCheck("10.0.1.1","0x44d77429f35c")}

      + Mode: shellscript

      + define <name> PRESENCE shellscript "<path> [<arg1>] [<argN>]..." [ <check-interval> [ <present-check-interval> ] ]
      +
      + Checks for a presence state via shell script. You can use a self-written script or binary in any language to obtain the presence state of a specific device (e.g. via SNMP check).

      + The shell must return 0 (absent) or 1 (present) on console (STDOUT). Any other values will be treated as an error

      + Example

      + define iPhone PRESENCE shellscript "/opt/check_device.sh iPhone"

      + Mode: event

      + define <name> PRESENCE event <absent-regexp> <present-regexp>
      +
      + Listens for events of other FHEM definitions to determine a presence state. You must provide two event regexp's in the same style as for the notify module.

      + If an event matches one of the provides regexps, the presence state will be changed.

      + Example

      + define Presence_John PRESENCE event Door_Switch:off Door_Switch:on

      + Mode: lan-bluetooth

      + Checks for a bluetooth device with the help of presenced or collectord. They can be installed where-ever you like, just must be accessible via network. + The given device will be checked for presence status.
      +
      + define <name> PRESENCE lan-bluetooth <bluetooth-address> <ip-address>[:port] [ <check-interval> ]
      +
      + The default port is 5111 (presenced). Alternatly you can use port 5222 (collectord)
      +
      + Example

      + define iPhone PRESENCE lan-bluetooth 0a:4f:36:d8:f9:89 127.0.0.1:5222

      + presenced

      +
        The presence is a perl network daemon, which provides presence checks of multiple bluetooth devices over network. + It listens on TCP port 5111 for incoming connections from a FHEM PRESENCE instance or a running collectord.
        +
        +Usage:
        +  presenced [-d] [-p <port>] [-P <filename>]
        +  presenced [-h | --help]
        +
        +
        +Options:
        +  -p, --port
        +     TCP Port which should be used (Default: 5111)
        +  -P, --pid-file
        +     PID file for storing the local process id (Default: /var/run/presenced.pid)
        +  -d, --daemon
        +     detach from terminal and run as background daemon
        +  -n, --no-timestamps
        +     do not output timestamps in log messages
        +  -v, --verbose
        +     Print detailed log output
        +  -h, --help
        +     Print detailed help screen
        +
        + + It uses the hcitool command (provided by a bluez installation) + to make a paging request to the given bluetooth address (like 01:B4:5E:AD:F6:D3). The devices must not be visible, but + still activated to receive bluetooth requests.

        + + If a device is present, this is send to FHEM, as well as the device name as reading.

        + + The presenced is available as:

        + +


      + lepresenced

      +
        lepresenced is a Perl network daemon that provides presence checks of + multiple bluetooth devices over network. In contrast to presenced, + lepresenced covers Bluetooth 4.0 (low energy) devices, i. e. + Gigaset G-Tags, FitBit Charges. + lepresenced listens on TCP port 5333 for connections of a PRESENCE definition + or collectord.
        +
        +Usage:
        +    lepresenced --bluetoothdevice <bluetooth device> --listenaddress <listen address> --listenport <listen port> --loglevel <log level> --daemon
        +    lepresenced -b <bluetooth device> -a <listen address> -p <listen port> -l <log level> -d
        +
        +valid log levels:
        +    LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Default: LOG_INFO
        +
        +Examples:
        +    lepresenced --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon
        +    lepresenced --loglevel LOG_DEBUG --daemon
        +
        + + To detect the presence of a device, it uses the command hcitool lescan (package: + bluez) to continuously listen to + beacons of Bluetooth LE devices. +

        + + If a device is present, this is send to FHEM, as well as the device name as reading.

        + + The presenced is available as:

        + +


      + collectord

      +
        + The collectord is a perl network daemon, which handles connections to several presenced installations to search for multiple bluetooth devices over network.

        + + It listens on TCP port 5222 for incoming connections from a FHEM presence instance. +
        +Usage:
        +  collectord -c <configfile> [-d] [-p <port>] [-P <pidfile>]
        +  collectord [-h | --help]
        +
        +
        +Options:
        +  -c, --configfile <configfile>
        +     The config file which contains the room and timeout definitions
        +  -p, --port
        +     TCP Port which should be used (Default: 5222)
        +  -P, --pid-file
        +     PID file for storing the local process id (Default: /var/run/collectord.pid)
        +  -d, --daemon
        +     detach from terminal and run as background daemon
        +  -n, --no-timestamps
        +     do not output timestamps in log messages
        +  -v, --verbose
        +     Print detailed log output
        +  -l, --logfile <logfile>
        +     log to the given logfile
        +  -h, --help
        +     Print detailed help screen
        +
        + Before the collectord can be used, it needs a config file, where all different rooms, which have a presenced detector, will be listed. This config file looks like: +

        +
        +    # room definition
        +    # ===============
        +    #
        +    [room-name]              # name of the room
        +    address=192.168.0.10     # ip-address or hostname
        +    port=5111                # tcp port which should be used (5111 is default)
        +    presence_timeout=120     # timeout in seconds for each check when devices are present
        +    absence_timeout=20       # timeout in seconds for each check when devices are absent
        +
        +    [living room]
        +    address=192.168.0.11
        +    port=5111
        +    presence_timeout=180
        +    absence_timeout=20
        +
        + + If a device is present in any of the configured rooms, this is send to FHEM, as well as the device name as reading and the room which has detected the device.

        + + The collectord is available as:

        + + +


      + +
    +
    + + Set +
      +
    • statusRequest - Schedules an immediatly check.
    • +
    • power - Executes the given power command which is set as attribute to power (on or off) the device (only when attribute "powerCmd" is set)
    • +
    • overrideInterval - Override the check interval to the given number of seconds. (not applicable in mode "event" and "lan-bluetooth")
    • +
    • clearOverride - clear an active check interval override (only if set command overrideInterval was executed before)
    • +
    +
    + + + Get +
      + N/A +
    +
    + + + Attributes

    +
      +
    • do_not_notify
    • +
    • readingFnAttributes

    • +
    • disable
    • + If this attribute is activated, an active check will be disabled.

      + Possible values: 0 => not disabled , 1 => disabled
      + Default Value is 0 (not disabled)

      +
    • absenceThreshold

    • (not applicable in mode "event" )
      + The number of checks that have to result in "absent" before the state of the PRESENCE definition is changed to "absent". + This can be used to verify the absence of a device with multiple check runs before the state is finally changed to "absent". + If this attribute is set to a value >1, the reading state and presence will be set to "maybe absent" during the absence verification.

      + Default Value is 1 (no absence verification)

      +
    • presenceThreshold

    • (not applicable in mode "event" )
      + The number of checks that have to result in "present" before the state of the PRESENCE definition is changed to "present". + This can be used to verify the permanent presence of a device with multiple check runs before the state is finally changed to "present". + If this attribute is set to a value >1, the reading state and presence will be set to "maybe present" during the presence verification.

      + Default Value is 1 (no presence verification)

      +
    • absenceTimeout

    • (only in mode "event" applicable)
      + The timeout after receiving an "absent" event, before the state of the PRESENCE definition is switched to "absent". + This can be used to verify the permanent absence by waiting a specific time frame to not receive an "present" event. + If this timeout is reached with no "present" event received in the meantime, the presence state will finally be set to "absent". + The timeout is given in HH:MM:SS format, where hours and minutes are optional. + If this attribute is set to a valid value, the reading state and presence will be set to "maybe absent" during the absence verification.

      + Default Value is 0 (no absence verification)

      +
    • presenceTimeout

    • (only in mode "event" applicable)
      + The timeout after receiving an "present" event, before the state of the PRESENCE definition is switched to "present". + This can be used to verify the permanent presence by waiting a specific time frame to not receive an "absent" event. + If this timeout is reached with no "absent" event received in the meantime, the presence state will finally be set to "present". + The timeout is given in HH:MM:SS format, where hours and minutes are optional. + If this attribute is set to a valid value, the reading state and presence will be set to "maybe present" during the presence verification.

      + Default Value is 0 (no presence verification)

      +
    • retryInterval

    • (Not applicable in mode "event" or "lan-bluetooth")
      + The check interval in case a check is prematurely aborted and was unable to check the presence. In this case, PRESENCE reschedules + the next check as retry within the given retry interval in seconds (usually lower than the regular check interval). +

      + Default Value is 10 seconds

      +
    • retryCount

    • (Not applicable in mode "event" or "lan-bluetooth")
      + The maximum number of checks to perform within the retryInterval in case a check is prematurely aborted and was unable to check the presence. + PRESENCE will try to retry after a failed check to a maximum of the given number of tries. If all retries fails also, it will uses afterwards + the regular check interval. +

      + Default Value is 3 (number of check retries)

      +
    • pingCount
    • (Only in mode "ping" applicable)
      + Changes the count of the used ping packets to recognize a present state. Depending on your network performance sometimes a packet can be lost or blocked.

      + Default Value is 4 (packets)

      +
    • bluetoothHciDevice
    • (Only in Mode "local-bluetooth" applicable)
      + Set a specific bluetooth HCI device to use for scanning. If you have multiple bluetooth modules connected, you can select a specific one to use for scanning (e.g. hci0, hci1, ...).

      +
    • fritzboxCheckSpeed
    • (Only in Mode "fritzbox" applicable)
      + When this attribute is enabled, the network speed is checked in addition to the device state.
      + This only makes sense for wireless devices connected directly to the FritzBox. +

      + Possible values: 0 => do not check speed, 1 => check speed when device is active
      + Default value is 0 (no speed check) +

      +
    • powerCmd

    • + Define a FHEM command, which powers on or off the device.

      + + When executing the powerCmd (set command: power) following placeholders will be replaced by there corresponding values:

      +
        +
      • $NAME - name of the PRESENCE definition
      • +
      • $ADDRESS - the address of the PRESENCE definition as given in the define statement
      • +
      • $ARGUMENT - the argument given to the power set command (e.g. "on" or "off)
      • +
      +
      + Example FHEM commands:

      +
        +
      • set PowerSwitch_1 on
      • +
      • set PowerSwitch_1 $ARGUMENT
      • +
      • "/opt/power_on.sh $ADDRESS"
      • +
      • {powerOn("$ADDRESS", "username", "password")}
      • +
      +
    +
    + + + Generated readings/events:

    +
      + General readings/events:

      +
        +
      • state: (absent|maybe absent|present|maybe present|disabled|error|timeout) - The state of the device, check errors or "disabled" when the disable attribute is enabled
      • +
      • presence: (absent|maybe absent|present|maybe present) - The presence state of the device. The value "maybe absent" only occurs if absenceThreshold is activated. The value "maybe present" only occurs if presenceThreshold is activated.
      • +
      • powerCmd: (executed|failed) - power command was executed or has failed
      • +


      + Bluetooth specific readings/events:

      +
        +
      • device_name: $name - The name of the Bluetooth device in case it's present
      • +


      + FRITZ!Box specific readings/events:

      +
        +
      • speed: $speed - The current speed of the checked device if attribute fritzboxCheckSpeed is activated
      • +


      + presenced/collectord specific readings/events:

      +
        +
      • command_accepted: $command_accepted (yes|no) - Was the last command acknowleged and accepted by the presenced or collectord?
      • +
      • room: $room - If the module is connected with a collector daemon this event shows the room, where the device is located (as defined in the collectord config file)
      • +
      +
    +
+ + +=end html + +=begin html_DE + + +

PRESENCE

+
    + Das PRESENCE Module bietet mehrere Möglichkteiten um die Anwesenheit von Handys/Smartphones oder anderen mobilen Geräten (z.B. Tablets) zu erkennen. +

    + Dieses Modul bietet dazu mehrere Modis an um Anwesenheit zu erkennen. Diese sind:

    +
      +
    • lan-ping - Eine Erkennung auf Basis von Ping-Tests im lokalen LAN/WLAN
    • +
    • fritzbox - Eine Erkennung aufgrund der internen Abfrage des Status auf der FritzBox (nur möglich, wenn FHEM auf einer FritzBox läuft)
    • +
    • local-bluetooth - Eine Erkennung auf Basis von Bluetooth-Abfragen durch den FHEM Server. Das Gerät muss dabei in Empfangsreichweite sein, aber nicht sichtbar sein
    • +
    • function - Eine Erkennung mithilfe einer selbst geschriebenen Perl-Funktion, welche den Anwesenheitsstatus ermittelt.
    • +
    • shellscript - Eine Erkennung mithilfe eines selbst geschriebenen Skriptes oder Programm (egal in welcher Sprache).
    • +
    • event - Eine Erkennung basierend auf Events einer anderen Definition in FHEM.
    • +
    • lan-bluetooth - Eine Erkennung durch Bluetooth-Abfragen via Netzwerk (LAN/WLAN) in ein oder mehreren Räumen
    • +
    +
    + Jeder Modus kann optional mit spezifischen Prüf-Intervallen ausgeführt werden.

    +
      +
    • check-interval - Das normale Prüfinterval in Sekunden für eine Anwesenheitsprüfung. Standardwert: 30 Sekunden
    • +
    • present-check-interval - Das Prüfinterval in Sekunden, wenn ein Gerät anwesend (present) ist. Falls nicht angegeben, wird der Wert aus check-interval verwendet
    • +
    +

    + + Define

    +
      Modus: lan-ping

      + define <name> PRESENCE lan-ping <IP-Addresse oder Hostname> [ <Interval> [ <Anwesend-Interval> ] ]
      +
      + Prüft ob ein Gerät über Netzwerk (üblicherweise WLAN) auf Ping-Anfragen reagiert und setzt entsprechend den Anwesenheitsstatus.

      + Beispiel

      + define iPhone PRESENCE lan-ping 192.168.179.21

      + Modus: fritzbox

      + define <name> PRESENCE fritzbox <Gerätename/MAC-Adresse> [ <Interval> [ <Anwesend-Interval> ] ]
      +
      + Prüft ob ein Gerät welches per WLAN mit der FritzBox verbunden ist, erreichbar durch Abfrage des Status mit dem Befehl ctlmgr_ctl. + Der Gerätename (Parameter: <Gerätename>) muss dem Namen entsprechen, welcher im Menüpunkt "Heimnetz" auf der FritzBox-Oberfläche angezeigt wird oder kann durch die MAC-Adresse im Format XX:XX:XX:XX:XX:XX ersetzt werden.

      + Dieser Modus ist nur verwendbar, wenn FHEM auf einer FritzBox läuft! Die Erkennung einer Abwesenheit kann ca. 10-15 Minuten dauern!

      + Beispiel

      + define iPhone PRESENCE fritzbox iPhone-6
      + define iPhone PRESENCE fritzbox 00:06:08:05:0D:00

      + Modus: local-bluetooth

      + define <name> PRESENCE local-bluetooth <Bluetooth-Adresse> [ <Interval> [ <Anwesend-Interval> ] ]
      +
      + Prüft ob ein Bluetooth-Gerät abgefragt werden kann und meldet dies als Anwesenheit. Für diesen Modus wird der Shell-Befehl "hcitool" benötigt + (wird durch das Paket bluez bereitgestellt), sowie ein funktionierender Bluetooth-Empfänger (intern oder als USB-Stick)

      + Beispiel

      + define iPhone PRESENCE local-bluetooth 0a:4f:36:d8:f9:8

      + Modus: function

      + define <name> PRESENCE function {...} [ <Interval> [ <Anwesend-Interval> ] ]
      +
      + Prüft den Anwesenheitsstatus mithilfe einer selbst geschriebenen Perl-Funktion (z.B. SNMP Abfrage).

      + Diese Funktion muss 0 (Abwesend) oder 1 (Anwesend) zurückgeben. Ein entsprechendes Beispiel findet man im FHEM-Wiki.

      + Beispiel

      + define iPhone PRESENCE function {snmpCheck("10.0.1.1","0x44d77429f35c")

      + Mode: shellscript

      + define <name> PRESENCE shellscript "<Skript-Pfad> [<arg1>] [<argN>]..." [ <Interval> [ <Anwesend-Interval> ] ]
      +
      + Prüft den Anwesenheitsstatus mithilfe eines selbst geschrieben Skripts oder Programmes (egal in welcher Programmier-/Skriptsprache)

      + Der Aufruf dieses Skriptes muss eine 0 (Abwesend) oder 1 (Anwesend) auf der Kommandozeile (STDOUT) ausgeben. Alle anderen Werte/Ausgaben werden als Fehler behandelt.

      + Beispiel

      + define iPhone PRESENCE shellscript "/opt/check_device.sh iPhone"

      + Mode: event

      + define <name> PRESENCE event <Abwesend-Regexp> <Anwesend-Regexp>
      +
      + Lauscht auf Events von anderen Definitionen innerhalb von FHEM um die Anwesenheit darzustellen. + Die regulären Ausdrücke für An- und Abwesenheit entsprechen dabei der Syntax von notify.

      + Sobald innerhalb von FHEM ein Event gefeuert wird, welches auf die Abwesend-Regexp bzw. Anwesend-Regexp passt, wird der Status entsprechend in PRESENCE gesetzt.

      + Beispiel

      + define Anwesenheit PRESENCE event Tuerschalter:off Tuerschalter:on

      + Modus: lan-bluetooth

      + Prüft ein Bluetooth-Gerät auf Anwesenheit über Netzwerk mit Hilfe von presenced oder collectord. Diese können auf jeder Maschine installiert werden, + welche eine Standard-Perl-Umgebung bereitstellt und über Netzwerk erreichbar ist. +
      +
      + define <name> PRESENCE lan-bluetooth <Bluetooth-Adresse> <IP-Adresse>[:Port] [ <Interval> ]
      +
      + Der Standardport ist 5111 (presenced). Alternativ kann man den Port 5222 (collectord) nutzen. Generell ist der Port aber frei wählbar.

      + Beispiel

      + define iPhone PRESENCE lan-bluetooth 0a:4f:36:d8:f9:89 127.0.0.1:5222

      + presenced

      +
        Der presenced ist ein Perl Netzwerkdienst, welcher eine Bluetooth-Anwesenheitserkennung von ein oder mehreren Geräten über Netzwerk bereitstellt. + Dieser lauscht standardmäßig auf TCP Port 5111 nach eingehenden Verbindungen von dem PRESENCE Modul oder einem collectord.
        +
        +Usage:
        +  presenced -d [-p <port>] [-P <filename>]
        +  presenced [-h | --help]
        +
        +
        +Options:
        +  -p, --port
        +     TCP Port which should be used (Default: 5111)
        +  -P, --pid-file
        +     PID file for storing the local process id (Default: /var/run/presenced.pid)
        +  -d, --daemon
        +     detach from terminal and run as background daemon
        +  -v, --verbose
        +     Print detailed log output
        +  -h, --help
        +     Print detailed help screen
        +
        + + Zur Bluetooth-Abfrage wird der Shell-Befehl "hcitool" verwendet (Paket: bluez) + um sogenannte "Paging-Request" an die gewünschte Bluetooth Adresse (z.B. 01:B4:5E:AD:F6:D3) durchzuführen. Das Gerät muss dabei nicht sichtbar sein, allerdings ständig aktiviert sein + um Bluetooth-Anfragen zu beantworten. +

        + + Wenn ein Gerät anwesend ist, wird dies an FHEM übermittelt zusammen mit dem Gerätenamen als Reading.

        + + Der presenced ist zum Download verfügbar als:

        + +


      + lepresenced

      +
        lepresenced ist ein Perl Netzwerkdienst, der analog zu presenced eine + Bluetooth-Anwesenheitserkennung von ein oder mehreren Geräten + über Netzwerk bereitstellt. Im Gegensatz zu presenced unterstützt + lepresenced Bluetooth 4.0 (Low Energy) Geräte wie z. B. Gigaset G-Tags, + FitBit Charges. + lepresenced lauscht standardmäßig auf TCP Port 5333 und wartet + auf eingehende Verbindungen des PRESENCE-Moduls bzw. von collectord.
        +
        +Usage:
        +    lepresenced --bluetoothdevice <bluetooth device> --listenaddress <listen address> --listenport <listen port> --loglevel <log level> --daemon
        +    lepresenced -b <bluetooth device> -a <listen address> -p <listen port> -l <log level> -d
        +
        +valid log levels:
        +    LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Default: LOG_INFO
        +
        +Examples:
        +    lepresenced --bluetoothdevice hci0 --listenaddress 127.0.0.1 --listenport 5333 --daemon
        +    lepresenced --loglevel LOG_DEBUG --daemon
        +
        + + Zur Bluetooth-Abfrage wird der Befehl hcitool lescan (Paket: + bluez) verwendet, der + fortwährend auf die Beacons der Bluetooth-LE-Geräte lauscht. +

        + + Wenn ein Gerät anwesend ist, wird dies an FHEM übermittelt zusammen mit dem Gerätenamen als Reading.

        + + Der le presenced ist zum Download verfügbar als:

        + +


      + collectord

      +
        + Der collectord ist ein Perl Netzwerk Dienst, welcher Verbindungen zu mehreren presenced-Instanzen verwaltet um eine koordinierte Suche nach ein oder mehreren Bluetooth-Geräten über Netzwerk durchzuführen.

        + + Er lauscht auf TCP port 5222 nach eingehenden Verbindungen von einem PRESENCE Modul. +
        +Usage:
        +  collectord -c <configfile> [-d] [-p <port>] [-P <pidfile>]
        +  collectord [-h | --help]
        +
        +
        +Options:
        +  -c, --configfile <configfile>
        +     The config file which contains the room and timeout definitions
        +  -p, --port
        +     TCP Port which should be used (Default: 5222)
        +  -P, --pid-file
        +     PID file for storing the local process id (Default: /var/run/collectord.pid)
        +  -d, --daemon
        +     detach from terminal and run as background daemon
        +  -v, --verbose
        +     Print detailed log output
        +  -l, --logfile <logfile>
        +     log to the given logfile
        +  -h, --help
        +     Print detailed help screen
        +
        + Bevor der collectord verwendet werden kann, benötigt er eine Konfigurationsdatei in welcher alle Räume mit einem presenced-Agenten eingetragen sind. Diese Datei sieht wie folgt aus: +

        +
        +    # Raum Definitionen
        +    # =================
        +    #
        +    [Raum-Name]              # Name des Raumes
        +    address=192.168.0.10     # IP-Adresse oder Hostname
        +    port=5111                # TCP Port welcher benutzt werden soll (standardmäßig 5111)
        +    presence_timeout=120     # Prüfinterval in Sekunden für jede Abfrage eines Gerätes, welches anwesend ist
        +    absence_timeout=20       # Prüfinterval in Sekunden für jede Abfrage eines Gerätes, welches abwesend ist
        +
        +    [Wohnzimmer]
        +    address=192.168.0.11
        +    port=5111
        +    presence_timeout=180
        +    absence_timeout=20
        +
        +
        + Wenn ein Gerät in irgend einem Raum anwesend ist, wird dies an FHEM übermittelt, zusammen mit dem Gerätenamen und dem Raum, in welchem das Gerät erkannt wurde.

        + + Der collectord ist zum Download verfügbar als:

        + + +
      +
    +
    + + + Set +
      +
    • statusRequest - Startet einen sofortigen Check.
    • +
    • power - Startet den powerCmd-Befehl welche durch den Parameter powerCmd angegeben ist (Nur wenn das Attribut "powerCmd" definiert ist)
    • +
    • overrideInterval - Übersteuert das Prüfinterval auf die übergebene Dauer in Sekunden (Nicht im Modus "event" und "lan-bluetooth" anwendbar)
    • +
    • clearOverride - Entfernt eine zuvor gesetzte Übersteuerung des Prüfintervals (Nur anwendbar, wenn zuvor eine Übersteuerung mit dem Set-Befehl overrideInterval stattgefunden hat)
    • +
    +
    + + + Get +
      + N/A +
    +
    + + + Attributes

    +
      +
    • do_not_notify
    • +
    • readingFnAttributes

    • +
    • disable
    • + Wenn dieses Attribut aktiviert ist, wird die Anwesenheitserkennung nicht mehr durchgeführt.

      + Mögliche Werte: 0 => Erkennung durchführen , 1 => Keine Erkennungen durchführen
      + Standardwert ist 0 (Erkennung durchführen)

      +
    • absenceThreshold
    • (Nicht im Modus "event" anwendbar)
      + Die Anzahl an Checks, welche in "absent" resultieren müssen, bevor der Status der PRESENCE-Definition auf "absent" wechselt. + Mit dieser Funktion kann man die Abwesenheit eines Gerätes verifizieren bevor der Status final auf "absent" geändert wird. + Wenn dieses Attribut auf einen Wert >1 gesetzt ist, werden die Readings "state" und "presence" auf den Wert "maybe absent" gesetzt, + bis der Status final auf "absent" wechselt.

      + Standardwert ist 1 (keine Abwesenheitsverifizierung)

      +
    • presenceThreshold
    • (Nicht im Modus "event" anwendbar)
      + Die Anzahl an Checks, welche in "present" resultieren müssen, bevor der Status der PRESENCE-Definition auf "present" wechselt. + Mit dieser Funktion kann man die Anwesenheit eines Gerätes verifizieren bevor der Status final auf "present" geändert wird. + Wenn dieses Attribut auf einen Wert >1 gesetzt ist, werden die Readings "state" und "presence" auf den Wert "maybe present" gesetzt, + bis der Status final auf "present" wechselt.

      + Standardwert ist 1 (keine Anwesenheitsverifizierung)

      +
    • absenceTimeout
    • (Nur im Modus "event" anwendbar)
      + Die Dauer, die nach einem "absent"-Event gewartet werden soll, bis der Status der PRESENCE-Definition tatsächlich auf "absent" geändert werden soll. + Die Dauer kann dabei im Format HH:MM:SS angegeben werden, wobei Stunden und Minuten optional sind. + Wenn dieses Attribut auf einen gültigen Wert gesetzt ist, werden die Readings "state" und "presence" bei einem "absent"-Event zunächst auf den Wert "maybe absent" gesetzt. + Sobald das parametrisierte Zeitfenster um ist, wird der Status final auf "absent" gesetzt.

      + Standardwert ist 0 Sekunden (keine Statusverzögerung)

      +
    • presenceTimeout
    • (Nur im Modus "event" anwendbar)
      + Die Dauer, die nach einem "present"-Event gewartet werden soll, bis der Status der PRESENCE-Definition tatsächlich auf "present" geändert werden soll. + Die Dauer kann dabei im Format HH:MM:SS angegeben werden, wobei Stunden und Minuten optional sind. + Wenn dieses Attribut auf einen gültigen Wert gesetzt ist, werden die Readings "state" und "presence" bei einem "present"-Event zunächst auf den Wert "maybe present" gesetzt. + Sobald das parametrisierte Zeitfenster um ist, wird der Status final auf "present" gesetzt.

      + Standardwert ist 0 Sekunden (keine Statusverzögerung)

      +
    • retryInterval
    • (Nicht im Modus "event" oder "lan-bluetooth" anwendbar)
      + Das Prüfinterval, welches im Falle eines vorzeitig abgebrochenen Checks genutzt wird, um eine Wiederholung auszuführen. Dazu wird im Falle eines abgebrochenen + Checks der nächste Check nach der übergebenen Dauer in Sekunden ausgeführt. Diese sollte geringer sein als das reguläre Prüfinterval. +

      + Standardwert ist 10 Sekunden

      +
    • retryCount
    • (Nicht im Modus "event" oder "lan-bluetooth" anwendbar)
      + Die maximale Anzahl an Wiederholungen, sollte ein Check vorzeitig abgebrochen werden. Sobald ein Check vorzeitigabbricht, werden maximal die übergebene Anzahl an Wiederholung + innerhalb des in retryInterval konfigurierten Interval ausgeführt um in kürzerer Zeit ein valides Ergebnis zu erhalten. +

      + Standardwert ist 3 Wiederholungen

      +
    • pingCount
    • (Nur im Modus "ping" anwendbar)
      + Verändert die Anzahl der Ping-Pakete die gesendet werden sollen um die Anwesenheit zu erkennen. + Je nach Netzwerkstabilität können erste Pakete verloren gehen oder blockiert werden.

      + Standardwert ist 4 (Versuche)

      +
    • bluetoothHciDevice
    • (Nur im Modus "local-bluetooth" anwendbar)
      + Sofern man mehrere Bluetooth-Empfänger verfügbar hat, kann man mit diesem Attribut ein bestimmten Empfänger auswählen, welcher zur Erkennung verwendet werden soll (bspw. hci0, hci1, ...). Es muss dabei ein vorhandener HCI-Gerätename angegeben werden wie z.B. hci0. +

      +
    • fritzboxCheckSpeed
    • (Nur im Modus "fritzbox")
      + Zusätzlich zum Status des Geräts wird die aktuelle Verbindungsgeschwindigkeit ausgegeben
      + Das macht nur bei WLAN Geräten Sinn, die direkt mit der FritzBox verbunden sind. Bei abwesenden Geräten wird als Geschwindigkeit 0 ausgegeben. +

      + Mögliche Werte: 0 => Geschwindigkeit nicht prüfen, 1 => Geschwindigkeit prüfen
      + Standardwert ist 0 (Keine Geschwindigkeitsprüfung) +

      +
    • powerCmd

    • + Ein FHEM-Befehl, welcher das Gerät schalten kann.

      + + Wenn der power-Befehl ausgeführt wird (set-Befehl: power) werden folgende Platzhalter durch ihre entsprechenden Werte ersetzt:

      +
        +
      • $NAME - Name der PRESENCE-Definition
      • +
      • $ADDRESS - Die überwachte Addresse der PRESENCE Definition, wie sie im define-Befehl angegeben wurde.
      • +
      • $ARGUMENT - Das Argument, was dem Set-Befehl "power" übergeben wurde. (z.B. "on" oder "off")
      • +
      +
      + Beispielhafte FHEM-Befehle:

      +
        +
      • set PowerSwitch_1 on
      • +
      • set PowerSwitch_1 $ARGUMENT
      • +
      • "/opt/power_on.sh $ADDRESS"
      • +
      • {powerOn("$ADDRESS", "username", "password")}
      • +
      +
    +
    + + + Generierte Readings/Events:

    +
      + Generelle ReadingsEvents:

      +
        +
      • state: (absent|maybe absent|present|maybe present|disabled|error|timeout) - Der Anwesenheitsstatus eine Gerätes (absent = abwesend; present = anwesend) oder "disabled" wenn das disable-Attribut aktiviert ist
      • +
      • presence: (absent|maybe absent|present|maybe present) - Der Anwesenheitsstatus eine Gerätes (absent = abwesend; present = anwesend). Der Wert "maybe absent" (vielleicht abwesend) tritt nur auf, sofern das Attribut absenceThreshold aktiviert ist. Der Wert "maybe present" (vielleicht anwesend) tritt nur auf, sofern das Attribut presenceThreshold aktiviert ist.
      • +
      • powerCmd: (executed|failed) - Ausführung des power-Befehls war erfolgreich.
      • +


      + Bluetooth-spezifische Readings/Events:

      +
        +
      • device_name: $name - Der Name des Bluetooth-Gerätes, wenn es anwesend (Status: present) ist
      • +


      + FRITZ!Box-spezifische Readings/Events:

      +
        +
      • speed: $speed - Die Netzwerkdeschwindigkeit des Gerätes, sofern das Attribut fritzboxCheckSpeed aktiviert ist.
      • +


      + presenced-/collectord-spezifische Readings/Events:

      +
        +
      • command_accepted: $command_accepted (yes|no) - Wurde das letzte Kommando an den presenced/collectord akzeptiert (yes = ja, no = nein)?
      • +
      • room: $room - Wenn das Modul mit einem collectord verbunden ist, zeigt dieses Event den Raum an, in welchem dieses Gerät erkannt wurde (Raumname entsprechend der Konfigurationsdatei des collectord)
      • +
      +
    +
+ + +=end html_DE + +=cut