Generic Presence Detection

Yes it’s normal (from a technical stand point) but there are of course ways around that. @MikeJMajor refered to the following solution I suppose. It’s reported to be quite reliable between many devices, including Androids and iPhones in doze/deepsleep mode:

Not convinced! … or beg to differ…

I run this set-up and it does not switch to ‘not present’, due to ARP retaining the IP.
… even (two) days later…

I’ve not done enough experiments to prove it, but I think hoping combined with Network binding might be a viable approach. I noticed when I was playing with it that periodically hpinging the iPhone made the Network Item linked to its ip work more reliably.

Also, if I remember correctly doesn’t the script first clear the arp table before doing the hping?

Very inspiring. Will try to imitate.
Thanks!

Based on this conversation, I decided to change my script to that from SeaSide, because it loops through multiple IPs. Tested this arvo, and it works; also, and yes, because of clearing the arp entry for the relevant IP. Nice.
Also adopted the MQTT publish approach, as this is what I will be using (Architecturally) whenever possible.

From a rights perspective I had to use sudo in front of the arp entry deletion, and add openhab to the sudoers without password.

No network binding used for this.
So basically, the shell script is triggered by a rule.
The script publishes to mosquitto topic;
OH subscribes to mosquitto topic

1 Like

@Max_G
Glad it could help someone. I prefer to not invoke the polling to see if a device is available from within openhab, since
I run it every two minutes for several devices, IMO it’s better suited to be run outside openhab in a crond-job or similar.
You don’t have to give openhab permissions to all commands in the sudoers table you can add

username ALL=(ALL) NOPASSWD: /sbin/ip

to allow allow username to be able to execute ip (for flushing that is). I’m sure you are aware of this, but just writing it since it seems a lot of people allow openhab to execute just about anything.

Regards, S

2 Likes

Just because I’m curious: With the two iPhones in my house, as soon as I integrate the arp flush, the script doesn’t work reliable. Without this line, it’s pretty solid and only misses a beat from time to time.

When you guys say it works for you, do you also use iPhones or only Android?

@jaydee73
One iphone 6 and two android devices Oneplus 3 and Onplus one.
The oneplus 3 is also pretty good at sleeping, and turning wireless network off, so I need a timeout.
For iphone I have 19 mins and 11 mins for android.

I have basically followed @rlkoshak post about presence.

I have:

val String LOG_NAME = "PRESENCE"
var Timer mJPhoneIPTimer = null

rule "JPhoneIPChange" 
when
    Item JPhoneIPMqtt changed from ON to OFF
then
     val int sleepTimerJ = (JPhoneIPSleepTimer.state as DecimalType).intValue
     if (PresenceOverride.state != ON) {
        logInfo(LOG_NAME, "J Phone is not home after setting timer to delay: " +   sleepTimerJ)
        try {
            mJPhoneIPTimer = createTimer(now.plusMinutes(sleepTimerJ), [|
                                         logInfo(LOG_NAME, "J Phone is not home after " + sleepTimerJ + " mins")
                                          JPhoneIP.sendCommand(OFF)
                                          NotifyPresence.postUpdate("J is not home on IP Phone time: " + sleepTimerJ)
                                         mJPhoneIPTimer = null
                                         ])    
        } catch (Exception x) { 
            logInfo(LOG_NAME,"Could not set value for sleep timer")
        }
     }
end

items:

Number   JPhoneIPSleepTimer "Oneplus 3 Sleep Timer [%d min]"   <clock>                   (gRestore) 
Switch   JPhoneIPMqtt       "OnePlus 3 Network Mqtt [%s]"                  <network>                     { mqtt="<[mosquitto:presence_sensors/network/j:state:default]" }
Switch   JPhoneIP           "OnePlus 3 Network [%s]"            <network>   (gJPresent, gPresent,gRestore) 
S

Sitemap:

 Setpoint item=JPhoneIPSleepTimer minValue=1 maxValue=60 step=1

I think the example in the wiki doesn’t take advantage of the powerful groups. I use:

Group:Switch:OR(ON, OFF)	group_presence						"Gruppe zur Anwesenheitserkennung"
Group:Switch:OR(ON, OFF)	group_movement						"Gruppe der Bewegungsmelder"				(group_presence)
rule "Zyklische Anwesenheitserkennung"
when
    Time cron "0 */5 * * * ?"
then
    if (presence.state == ON) {
        if (group_presence.state == OFF) {
	    if (!group_presence.changedSince(now.minusMinutes(5)) sendCommand(presence, OFF)
        }
    }
    else {
        if (group_presence.state == ON) sendCommand(presence, ON)
        else if (presence.state == NULL) sendCommand(presence, OFF)
    }
end

rule "Eventbasierte Anwesenheitserkennung"
when
	Item group_presence changed from OFF to ON
then
    if (presence.state != ON) sendCommand(presence, ON)
end

which is really easy to understand…

iPhones – actually forgot my Android… will test some day…

No I wasn’t :slight_smile: Thank you for mentioning it.

In the end, you could use Present as the timer so long as ON keeps getting sent to it while you are present every <n minutes where n is the timer length.

I did just that. My presence detection is based on the Fritz!Box (router) TR065 binding. The binding updates presence items (MAC addresses of iPhones) once per minute. So there is my “every x minutes” trigger to keep the timers running.

The Fritz!Box presence detection is generally very reliable. However, there are two catches: Sometimes a MAC will be falsely reported as being absent for a short period of time. Especially at night, when the phones are on standby. I have learned from experience that this generally happens within a timeframe of less than five minutes. Secondly, the moment a MAC becomes (really) absent, it sometimes gets reported as being present again directly after the absence and then (finally) absent again within seconds. It then stays absent reliably.

The solution is what you proposed, and it is beautifully elegant. I cannot use the presence Items controlled by the binding directly for the reasons mentioned above. So they serve as the triggers for the “real” presence items I then use in other rules. The “real” presence items expire after 5 minutes if not updated. In other words, presence is detected immediately and absence is detected with a 5min delay. (Actually, just for completeness’ sake: Absence is detected with a 20mind delay because the Fritz!Box reports absence with a delay of 15min. This seems to be built into the firmware and cannot be helped.)

Note that I also use “arrival” items. I have defined a timeframe of 5min in which someone is in the state of arriving. E.g. the front door light would be switched on during that time (if it is dark, etc.). Those are just “sugar” items that are not really necessary. The advantage being that the arrival timeframe can be defined in the items definition file as well.

Items:

// Fritz!Box presence items

// iPhone A
Switch PresenceFritzA "A (Fritz) [MAP(presence.map):%d]" <present> {fritzboxtr064="maconline:xx-xx-xx-xx-xx-xx"}
// iPhone B
Switch PresenceFritzB "B (Fritz) [MAP(presence.map):%d]" <present> {fritzboxtr064="maconline:xx-xx-xx-xx-xx-xx"}

// if at least one person is present (ON), the group is present (ON), else absent (OFF)
Group:Switch:OR(ON, OFF) PresenceAnyone "Anyone [MAP(presence.map):%d]" <present>
// be present immediately but be absent after (not receiving presence update for) five minutes
Switch PresenceA "A [MAP(presence.map):%d]" <present> (PresenceAnyone) {expire="5m,command=OFF"}
Switch PresenceB "B [MAP(presence.map):%d]" <present> (PresenceAnyone) {expire="5m,command=OFF"}

// if at least one person is arriving (ON), anyone is arriving (ON) else not (OFF)
Group:Switch:OR(ON, OFF) ArrivalAnyone "Anyone [MAP(arrival.map):%d]"
// status "arriving" is kept for five minutes
Switch ArrivalA "A [MAP(arrival.map):%d]" (ArrivalAnyone) {expire="5m,command=OFF"}
Switch ArrivalB "B [MAP(arrival.map):%d]" (ArrivalAnyone) {expire="5m,command=OFF"}

// There is no XOR for groups so we need to use an Item and a rule
Switch ArrivalFirst "First [MAP(arrival.map):%d]" {expire="5m,command=OFF"}

Rules:

rule "A (Fritz) present or absent"
when
	Item PresenceFritzA received update ON
then
	PresenceA.sendCommand(ON);	
end

rule "B (Fritz) present or absent"
when
	Item PresenceFritzB received update ON
then
	PresenceB.sendCommand(ON);	
end

rule "A arriving"
when
	Item PresenceA changed to ON
then
	ArrivalA.sendCommand(ON)
end

rule "B arriving"
when
	Item PresenceB changed to ON
then
	ArrivalB.sendCommand(ON)
end

rule "First arrival"
when
	Item PresenceAnyone changed to ON
then
	ArrivalFirst.sendCommand(ON)
end

Thank you very much for that elegant solution. It helped me do away with tons of lines of cumbersome timer code as well.

4 Likes

This indeed looks a lot cleaner than the example. And as far as I can judge it has no disadvantage in the behaviour of the rule. Nice work!

I have a weird problem that the timer never expires. I have two sensors, an owntracks sensor and a phone IP

Group:Switch:AND(OFF,ON) gPresent <present> // all presence sensors belong to this group
Switch Present "Someone is Present" <present> // turns off 5 minutes after everyone leaves
Switch Present_Timer { expire="5m,command=OFF" }
Switch  PhoneBT  "Me @ Home" <bluetooth>    (gPresent)   { mqttitude="mosquitto:owntracks/me/phone/event:Home" }
Switch  PhoneIP <network>  (gPresent) {channel="network:device:d12435eb:online"}

The rules file looks like this

rule "System startup"
        when
                System started
        then
                Present.sendCommand(OFF) // assume no one is home on startup
                PhoneBT.sendCommand(OFF) // assume phone BT is not reachable 
end


rule "gPresent updated, at least one sensor changed state"
when
    Item gPresent received update
then
    logInfo("Debug", "------gPresent updated------")
    gPresent.members.forEach [s | logInfo("Debug", s.name + " equals " + s.state.toString)]
    logInfo("Debug", gPresent.name + " equals " + gPresent.state.toString)
    logInfo("Debug", Present.name + " equals " + Present.state.toString)
    // someone came home
    if(gPresent.state == ON && Present.state != ON) {
        Present_Timer.postUpdate(OFF) // cancel the timer if necessary
        Present.sendCommand(ON)
        logInfo("Debug", "Someone came home")
    }

    // no one is home
    else if(gPresent.state == OFF && Present.state != OFF){
        Present_Timer.sendCommand(ON) // start the timer
        logInfo("Debug", "No one is home after five minutes")
    }
end

rule "Present_Timer expired"
when
    Item Present_Timer received command OFF
then
    Present.sendCommand(OFF)
end

When both the sensors go off the logs show that the timer is switched on. However, the timer continues inifinitely. I dont understand why the group rule is getting update every few seconds, since the events dont show the update. The log file looks like so

2017-08-23 19:40:18.657 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneBT equals OFF
2017-08-23 19:40:18.657 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneIP equals OFF
2017-08-23 19:40:18.657 [INFO ] [eclipse.smarthome.model.script.Debug] - gPresent equals OFF
2017-08-23 19:40:18.658 [INFO ] [eclipse.smarthome.model.script.Debug] - Present equals ON
2017-08-23 19:40:18.659 [INFO ] [eclipse.smarthome.model.script.Debug] - No one is home after five minutes
2017-08-23 19:40:23.600 [INFO ] [eclipse.smarthome.model.script.Debug] - ------gPresent updated------
2017-08-23 19:40:23.600 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneBT equals OFF
2017-08-23 19:40:23.601 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneIP equals OFF
2017-08-23 19:40:23.601 [INFO ] [eclipse.smarthome.model.script.Debug] - gPresent equals OFF
2017-08-23 19:40:23.601 [INFO ] [eclipse.smarthome.model.script.Debug] - Present equals ON
2017-08-23 19:40:23.602 [INFO ] [eclipse.smarthome.model.script.Debug] - No one is home after five minutes
2017-08-23 19:40:38.694 [INFO ] [eclipse.smarthome.model.script.Debug] - ------gPresent updated------
2017-08-23 19:40:38.694 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneBT equals OFF
2017-08-23 19:40:38.695 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneIP equals OFF
2017-08-23 19:40:38.695 [INFO ] [eclipse.smarthome.model.script.Debug] - gPresent equals OFF
2017-08-23 19:40:38.695 [INFO ] [eclipse.smarthome.model.script.Debug] - Present equals ON
2017-08-23 19:40:38.696 [INFO ] [eclipse.smarthome.model.script.Debug] - No one is home after five minutes
.......
2017-08-23 19:49:59.298 [INFO ] [eclipse.smarthome.model.script.Debug] - ------gPresent updated------
2017-08-23 19:49:59.298 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneBT equals OFF
2017-08-23 19:49:59.299 [INFO ] [eclipse.smarthome.model.script.Debug] - PhoneIP equals OFF
2017-08-23 19:49:59.299 [INFO ] [eclipse.smarthome.model.script.Debug] - gPresent equals OFF
2017-08-23 19:49:59.299 [INFO ] [eclipse.smarthome.model.script.Debug] - Present equals ON
2017-08-23 19:49:59.300 [INFO ] [eclipse.smarthome.model.script.Debug] - No one is home after five minutes

The events logs shows that the timer is being called continuously,

2017-08-23 19:40:18.654 [ItemStateChangedEvent     ] - PhoneBT changed from ON to OFF
2017-08-23 19:40:18.654 [GroupItemStateChangedEvent] - gPresent changed from ON to OFF through PhoneBT
2017-08-23 19:40:18.659 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:40:23.603 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:40:38.697 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:40:53.971 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:41:09.411 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:41:24.915 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:41:40.071 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:41:55.125 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:42:10.231 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:42:25.477 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:42:40.577 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:42:55.769 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:43:10.923 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
..........
2017-08-23 19:49:13.757 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:49:28.993 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:49:44.084 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON
2017-08-23 19:49:59.301 [ItemCommandEvent          ] - Item 'Present_Timer' received command ON

Why is the Present_Timer being called every few seconds and why it doesnt stop after 5 minutes?

Have you installed the expire binding?

Since the time keeps getting command ON faster than every five minutes the timer will never go off.

Try adding a check to see if the Timeer is already ON in your else if and only send the command if it is OFF.

It looks like your rule keeps getting repeated away updates so the timer never gets a chance to expire.

Ah, I dont have that installed!

Something like this?

    // no one is home
    else if(gPresent.state == OFF && Present.state != OFF){
        if (Present_Timer.state == OFF) {
        Present_Timer.sendCommand(ON) // start the timer
        logInfo("Debug", "No one is home after five minutes")
        }

That would work.

Never thought about using that one for presence detection!!

Is it possible to map 5m to an number item somehow?