Presence Detection Rule

Trying to do a script for presense detection. Found one online that was for checking phones connecting to wireless router. I believe I have that working correctly. Now I’m trying to set it up with motion detectors in my home.
Not sure its setup right or not. I am trying to figure out whats the difference between these 3 lines in the rule.
if(GRoom22.members.filter(s | s.state == OPEN).size == 0)
if(GRoom22.members.filter(s | s.state == OPEN).size > 0) {
if(GRoom22.members.filter(s | s.state == OPEN).size > 0) {
Also does anyone have a better rule for presense detection? Is there away to combine the rules. Thanks for any help.

rule “Periodically check motion presence”
when
Time cron “0 */5 * * * ?”
then
if (motioncheck.state == ON)
{
if(GRoom22.members.filter(s | s.state == OPEN).size == 0) {
logInfo(“PresenceCheck”, “No motion, checking for flapping”)
if(GRoom22.members.filter(s | s.changedSince(now.minusMinutes(5))).size == 0) {
logInfo(“PresenceCheck”, “Nobody is at home”)
sendCommand(motioncheck, OFF)
}
}
}
else
{
//For initialization. If Presence is undefined or off, although it should be on.
if(GRoom22.members.filter(s | s.state == OPEN).size > 0) {
sendCommand(motioncheck, ON)
}
else if (motioncheck.state == Undefined || motioncheck.state == Uninitialized) {
sendCommand(motioncheck, OFF)
}
}

end

rule “Coming home”
when
Item GRoom22 changed
then
if (motioncheck.state != ON) {
if(GRoom22.members.filter(s | s.state == OPEN).size > 0) {
logInfo(“PresenceCheck”, “Somebody is home”)
sendCommand(motioncheck, ON)
}
}
end

Here is how I approached it in my setup. I’m using some Bluetooth detectors instead of PIRs but the concept is the same.

I make all of my presence detectors to be Switches.

I put all my presence detectors into a gPresence Group

In your case I would probably create a Proxy switch for your motion sensors and create a Timer to switch the Switch from ON to OFF after a certain period of time so that a PIR going to OPEN lasts longer than the sensor would report. I’ve seen five minutes be pretty common timeout. See @bob_dickenson’s posting about Proxy items here. for an example.

I think this is far better than polling.

Then you can create a rule that triggers on updates to gPresence and if all the member switches are OFF switch a Present swtich Item to OFF. You can use a timer here as well so all the gPresence switched have to be off for five minutes before switching the main Present swtich to OFF to cover cases like going to check the mail.

Rules that care about your presence would refer to the state of Present.

Here are my rules (they don’t include the proxy idea mentioned above, see the link for an example of that) for reference. NOTE: these rules are the result of almost a year’s worth of learning. My original rule was over 300 lines lone and didn’t really work very well.

Items:

Switch Present "Someone is Present" <present> // turns off 5 minutes after everyone leaves

Group gPresent     "Present group" <present>
Group gRichPresent "Rich Present" <present>
Group gWifePresent "Wife Present" <present>

// Used to tell if a client is reconnected
Switch   S_V_RichPhoneIP   <network>   (gRichPresent, gPresent)                    { nh="192.168.1.102" }
Switch   S_V_RichChimeraBT   <bluetooth> (gRichPresent, gPresent, gChimeraSensors) { mqtt="<[mosquitto:presence_sensors/bluetooth/chimeraRich:state:default]" }
Switch   S_V_RichGarageaBT   <bluetooth> (gRichPresent, gPresent, gGarageSensors)  { mqtt="<[mosquitto:presence_sensors/bluetooth/garageRich:state:default]" }
Switch   S_V_RichHydraBT   <bluetooth> (gRichPresent, gPresent, gHydraSensors)     { mqtt="<[mosquitto:presence_sensors/bluetooth/hydraRich:state:default]" }

Switch  S_V_WifePhoneIP   <network>   (gWifePresent, gPresent)                     { nh="192.168.1.103" }
Switch  S_V_WifeChimeraBT   <bluetooth> (gWifePresent, gPresent, gChimeraSensors)  { mqtt="<[mosquitto:presence_sensors/bluetooth/chimeraWife:state:default]" }
Switch  S_V_WifeGarageBT   <bluetooth> (gWifePresent, gPresent, gGarageSensors)    { mqtt="<[mosquitto:presence_sensors/bluetooth/garageWife:state:default]" }
Switch  S_V_WifeHydraBT   <bluetooth> (gWifePresent, gPresent, gHydraSensors)      { mqtt="<[mosquitto:presence_sensors/bluetooth/hydraWife:state:default]" }

Rule:

import org.openhab.model.script.actions.*

var Timer presenceTimer = null

rule "gPresent changed"
when
    Item gPresent received update
then

    val isPresent = gPresent.allMembers.filter(s | s.state == ON).size > 0

    // Someone came home
    if(isPresent && Present.state != ON) {
            logInfo("Presence", "Someone is home")
            if(presenceTimer != null) {
                    presenceTimer.cancel
                    presenceTimer = null
            }
            Present.sendCommand(ON)
    }

    // Everyone is gone
    else if(!isPresent && Present.state != OFF) {
            logInfo("Presence", "No one is home, setting timer")
            if(presenceTimer != null) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
                    logInfo("Presence", "No one is home after five minutes")
                    if(Present.state != OFF) Present.sendCommand(OFF)
            ])
    }
end

NOTE: If you indent your lines by four spaces it will treat your lines as code which makes it soooo much easier to read postings with code.

3 Likes

Thanks Rich. I got it working.

I would like to add the “Wasp in a Room” method into my occupancy script. I want it to say that if there is motion or phone detection in the house, but then there is none, but no entrance door is opened during that time that the homeoraway switch would still stay on. This would obviously most likely occur when everyone is sleeping but also if no one is moving around and there phone is not connecting to the wireless router at the time. Would this be difficult to add or would it require an extra line or two. Any help would be appreciated.

import org.openhab.model.script.actions.*

var Timer presenceTimer = null
var Number counter = 0
var Number lastCheck = 0
var Number counter1 = 0
var Number lastCheck1 = 0
var Number counter2 = 0
var Number lastCheck2 = 0
var Number counter3 = 0
var Number lastCheck3 = 0


rule "Motion Check"
when
     Item GRoom22 received update
then

    val isPresent = GRoom22.allMembers.filter(s | s.state == ON).size > 0

    // Someone came home
    if(isPresent && homeoraway.state != ON) {
            logInfo("Presence", "Someone is home")
            if(presenceTimer != null) {
                    presenceTimer.cancel
                    presenceTimer = null
            }
            homeoraway.sendCommand(ON)
    }

    // Everyone is gone
    else if(!isPresent && homeoraway.state != OFF) {
            logInfo("Presence", "No one is home, setting timer")
            if(presenceTimer != null) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
                    logInfo("Presence", "No one is home after five minutes")
                    if(homeoraway.state != OFF) homeoraway.sendCommand(OFF)
            ])
    }
end

rule "Turn on Foyer Motion Switch"
    when   
            Item FoyerMotion changed from CLOSED to OPEN
    then
            counter = counter + 1 {
            sendCommand(Foyer, ON)
            }
    end

rule "Turn off Foyer Motion Switch"
    when   
            Time cron "0 0/3 * 1/1 * ? *"
    then

            if(lastCheck == counter) {
            counter = 0
            lastCheck = -1;
            sendCommand(Foyer, OFF)
            } else {
                    lastCheck = counter
            }
    end   

rule "Turn on Living Room Motion Switch"
    when   
            Item LivingRoomMotion changed from CLOSED to OPEN
    then
            counter1 = counter1 + 1 {
            sendCommand(LivingRoom, ON)
            }
    end

rule "Turn off Living Room Motion Switch"
    when   
            Time cron "0 0/3 * 1/1 * ? *"
    then

            if(lastCheck1 == counter1) {
            counter1 = 0
            lastCheck1 = -1;
            sendCommand(LivingRoom, OFF)
            } else {
                    lastCheck1 = counter1
            }
    end   

rule "Turn on Up Stairs Motion Switch"
    when   
            Item UpStairsMotion changed from CLOSED to OPEN
    then
            counter2 = counter2 + 1 {
            sendCommand(UpStairs, ON)
            }
    end

rule "Turn off Up Stairs Motion Switch"
    when   
            Time cron "0 0/3 * 1/1 * ? *"
    then

            if(lastCheck2 == counter2) {
            counter2 = 0
            lastCheck2 = -1;
            sendCommand(UpStairs, OFF)
            } else {
                    lastCheck2 = counter2
            }
    end   

rule "Turn on Basement Motion Switch"
    when   
            Item BasementMotion changed from CLOSED to OPEN
    then
            counter3 = counter3 + 1 {
            sendCommand(Basement, ON)
            }
    end

rule "Turn off Basement Motion Switch"
    when   
            Time cron "0 0/3 * 1/1 * ? *"
    then

            if(lastCheck3 == counter3) {
            counter3 = 0
            lastCheck3 = -1;
            sendCommand(Basement, OFF)
            } else {
                    lastCheck3 = counter3
            }
     end

This would require some extra code and a slight restructuring of how you determine presence. Off the top of my head you will need:

  • You no longer need/want the timer to turn off homeoraway after five minutes. Leave homeoraway ON even if all the members of GRoom22 are OFF
  • Add sensors to your doors if they are not already present
  • Add Contact Items for your door sensors if not already present
  • Add rules for your Door Contact Items. In these rules check GRoom22’s members and if all of them are OFF then set homeoraway to OFF in that rule

You will need to experiment as there are certainly a bunch of edge cases and possibly some race conditions that could come into play. For example, is it even possible to leave the house (i.e. open a door) without triggering one of the motion detectors? If not this design pattern may end up keeping you “home” even when you are away as the Motion Sensor is still reporting ON after you have already closed the door and the door will not open again until you return.

The Wasp in the Box is a great design pattern but not always suitable if you have an ambiguous set of sensor readings or timing issues.

Hi Rich,
I appreciate your help. I already have sensors on all my entrance doors and have the contacts items. I put them in GRoom21. There are no motion sensors by my garage door that we usually use to go some where. Is there a way to just change the language on the “//Everyone is gone” section to occur if the garage door opens and then still has a 5 minute delay before it turns the homeoraway switch to away. That should give the phones time to drop from being active on my router.

Personally I would create a new rule that triggers only on the garage door opening and move the “Everyone is gone” code to that rule.

So your GRoom22 rule will keep the // Someone came home section and your GRoom21 rule would only include the //Everyone is gone section.

These rules will have to be in the same .rules file so they both have access to presenceTimer.

Rich,
Could you check my script below. It’s not working correctly. When I open and close my door the script turns my homeoraway switch to “OFF” after the 5 minute timer runs out even though a phone switch is “ON”. I also get the following error: 2016-01-13 09:09:00.807 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule ‘Presence Detected’: cannot invoke method public abstract boolean org.openhab.model.script.actions.Timer.cancel() on null.

rule "Presence Detected"
when
    Item GRoom22 received update
then

    val isPresent = GRoom22.allMembers.filter(s | s.state == ON).size > 0

    // Someone came home
    if(isPresent && homeoraway.state != ON) {
            logInfo("Presence", "Someone is home")
            if(presenceTimer != null) {
                    presenceTimer.cancel
                    presenceTimer = null
            }
            homeoraway.sendCommand(ON)
    }
end

rule "Presence not Detected"
when
    Item GRoom21 received update    
then
            val notPresent = GRoom22.allMembers.filter(s | s.state == OFF).size > 0
                           
            logInfo("Presence", "Verifying if anyone is home")
            if(presenceTimer != null) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
                    if(notPresent && homeoraway.state != OFF){
                    logInfo("Presence", "No one is home")
                    homeoraway.sendCommand(OFF)
                    sendMail("xxxxx@xxxxx.net", "Occupancy", "No One's Home")}
            ])
end

You need to recheck/recalculate notPresent within the body of the timer. Right now your timer is using the notPresent value that was calculated five minutes ago which could have changed.

rule "Presence not Detected"
...
    presenceTimer = createTimer(now.plusMinutes(5), [|
        val np = GRoom22.allMembers.filter(s | s.state == OFF).size > 0
        if(np && homeoraway.state != OFF)
...

I get that error periodically sometimes too and have never been able to make any sense out of it. You explicitly check that the timer is not null so how can it be null in the very next statement?

A light bulb just popped up over my head. I wonder if trying to cancel an already terminated timer throws a null exception. Try adding this to see if the error goes away:

if(presenceTimer != null && !presenceTimer.hasTerminated) {

OK, made that change with the notpresent value. I was wondering if I have the notpresent line correct. There are a combination of 9 sensors in the GRoom22 made up of motion switches and virtual phone switches. Is that line saying if only 1 of the items is “OFF” that notpresent is true or if all 9 of the items are “OFF”? I know for Presence detected rule I only needed a a minimum of 1 to turn on the homeoraway switch, but with Presence not detected rule I would need all the items off.

Good point. I didn’t double check that line.

For notPresent you want it to be true only if all the Items are OFF so the line should be

val notPresent = GRoom22.allMembers.filter[s | s.state == ON].size > 0

I’ve been experimenting with it and it appears to be working. The only problem I see is when I return home and open the door the log shows, after the 5 minute delay, “No one is home” and then 2 seconds later it shows “Someone is home”. It appears that the “Presence not Detected” rule is still be activated even though after 5 minutes I’m sure that our phones have already connected to the router and we’ve past by motion detectors. Do you see anything wrong that would cause this? This should be my last post on this subject so I definitely want to tell you thanks for all your help!

rule "Presence not Detected"
when
    Item GRoom21 received update    
then
            logInfo("Presence", "Verifying if anyone is home")
            if(presenceTimer != null) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
            val notPresent = GRoom22.allMembers.filter[s | s.state == OFF].size > 0
                    if(notPresent && homeoraway.state != OFF){
                    logInfo("Presence", "No one is home")
                    homeoraway.sendCommand(OFF)
                    sendMail("xxxx@xxxx.net", "Occupancy", "No One's Home")}
        ])

end

1 Like

You still have notPresent being set to true if one or more sensor is OFF. What you want to know is if ALL are off. You can check that by seeing if none are ON.

GRoom22.allMembers.filter[s | s.state == ON].size == 0

I see that my example above was in error.

Rich, I’ve implemented your presence rule it’s working well except for one part. It wont cancel the timer if I go from OFF to ON within the 5 minutes. The weird part is the timmer does seem to go to null. I also don’t know why it logs everything twice… Any help you or anyone else can provide will be greatly appreciated!

Logs:

2016-02-20 12:17:13.863 [INFO ] [.openhab.model.script.Presence] - No one is home, setting timer
2016-02-20 12:17:13.865 [INFO ] [.openhab.model.script.Presence] - No one is home, setting timer 
2016-02-20 12:21:54.469 [INFO ] [.openhab.model.script.Presence] - Someone is home                
2016-02-20 12:21:54.471 [INFO ] [.openhab.model.script.Presence] - Someone is home                
2016-02-20 12:22:13.888 [INFO ] [.openhab.model.script.Presence] - No one is home after five minutes

2016-02-20 12:21:40.729 [INFO ] [rg.openhab.model.script.mydata] - #false#
2016-02-20 12:21:40.730 [INFO ] [rg.openhab.model.script.mydata] - #false#
2016-02-20 12:21:54.468 [INFO ] [rg.openhab.model.script.mydata] - #true#
2016-02-20 12:21:54.469 [INFO ] [rg.openhab.model.script.mydata] - #true#
2016-02-20 12:21:54.470 [INFO ] [rg.openhab.model.script.mydata] - #org.openhab.model.script.internal.actions.TimerImpl@2d18a36e#
2016-02-20 12:21:54.471 [INFO ] [rg.openhab.model.script.mydata] - #null#
2016-02-20 12:21:54.472 [INFO ] [rg.openhab.model.script.mydata] - #null#

Items:

Group gPresent "Present group"  <present>                                                      
Group gPresence "Presence group"        <present>                                              
Switch  Present "Someone is Present"    <present> (gPresence) // turns off 5 minutes
e leaves                                                                                          
Switch  WifePhoneMac   <network>       (gWifePresent, gPresent, gPresence)                       
Switch  ShawnsPhoneMac  <network>       (gShawnPresent, gPresent, gPresence)               

Rule:

import org.openhab.model.script.actions.*

var Timer presenceTimer = null

rule "gPresent changed"
when
        Item gPresent received update
then

 val isPresent = gPresent.allMembers.filter(s | s.state == ON).size > 0
      logInfo("mydata", "#" + isPresent + "#") // log variable

    // Someone came home
    if(isPresent != false && Present.state != OFF) {
            logInfo("Presence", "Someone is home")
            logInfo("mydata", "#" + presenceTimer + "#") // log variable
            if(presenceTimer != null && !presenceTimer.hasTerminated) {
                    presenceTimer.cancel
                    presenceTimer = null
                    logInfo("mydata", "#" + presenceTimer + "#") // log variable
            }
            Present.sendCommand(ON)
    }

    // Everyone is gone
    else if(!isPresent != true && Present.state != ON) {
            logInfo("Presence", "No one is home, setting timer")
            if(presenceTimer != null && !presenceTimer.hasTerminated) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
                    logInfo("Presence", "No one is home after five minutes")
                    if(Present.state != OFF) Present.sendCommand(OFF)
            ])
    }
end

I thought I had it working but I’m still not getting the timer canceled. This is what I have so far

Logs:

2016-02-20 13:50:28.889 [INFO ] [.openhab.model.script.Presence] - No one is home, setting timer
2016-02-20 13:50:28.890 [INFO ] [.openhab.model.script.Presence] - No one is home, setting timer
2016-02-20 13:50:39.228 [INFO ] [rg.openhab.model.script.mydata] - #true#
2016-02-20 13:50:39.229 [INFO ] [rg.openhab.model.script.mydata] - #ON#
2016-02-20 13:50:39.232 [INFO ] [rg.openhab.model.script.mydata] - #true#
2016-02-20 13:50:39.233 [INFO ] [rg.openhab.model.script.mydata] - #ON#
2016-02-20 13:50:39.239 [INFO ] [.openhab.model.script.Presence] - Someone is home, timmer canceled
2016-02-20 13:50:39.240 [INFO ] [.openhab.model.script.Presence] - Someone is home, timmer canceled
2016-02-20 13:50:39.249 [INFO ] [rg.openhab.model.script.mydata] - #org.openhab.model.script.internal.actions.TimerImpl@1c39039f#
2016-02-20 13:50:39.250 [INFO ] [rg.openhab.model.script.mydata] - #org.openhab.model.script.internal.actions.TimerImpl@1c39039f#
2016-02-20 13:50:39.267 [INFO ] [rg.openhab.model.script.mydata] - #null#
2016-02-20 13:50:39.270 [INFO ] [rg.openhab.model.script.mydata] - #null#
2016-02-20 13:55:28.892 [INFO ] [.openhab.model.script.Presence] - No one is home after five minutes

Rule:

import org.openhab.model.script.actions.*

var Timer presenceTimer = null

rule "gPresent changed"
when
        Item gPresent received update
then

    val isPresent = gPresent.allMembers.filter(s | s.state == ON).size > 0
    logInfo("mydata", "#" + isPresent + "#") // log variable
    logInfo("mydata", "#" + Present.state + "#") // log variable
    // Someone came home
    if(isPresent != false && Present.state != ON) {
            logInfo("Presence", "Someone is home")
            logInfo("mydata", "#" + presenceTimer + "#") // log variable
            Present.sendCommand(ON)
    }
    if(presenceTimer != null && !presenceTimer.hasTerminated) {
            logInfo("Presence", "Someone is home, timmer canceled")
            logInfo("mydata", "#" + presenceTimer + "#") // log variable
            presenceTimer.cancel
            presenceTimer = null
            logInfo("mydata", "#" + presenceTimer + "#") // log variable
    }

    // Everyone is gone
    if(isPresent != true && Present.state != OFF) {
            logInfo("Presence", "No one is home, setting timer")
            if(presenceTimer != null && !presenceTimer.hasTerminated) {
                    presenceTimer.cancel
            }
            presenceTimer = createTimer(now.plusMinutes(5), [|
                    logInfo("Presence", "No one is home after five minutes")
                    if(Present.state != OFF) Present.sendCommand(OFF)
            ])
    }
end

It looks like it might be a timing issue. The rule is being triggered twice for each update and it looks like the code it running with just the right timing that it is creating two timers but losing the reference to the first timer when it creates the second one. Probably the easiest way around this would be to wrap the rule in an reentrant lock so only one instance of the rule can run at a time. This will prevent this sort of timing problem.

// other imports
import java.util.concurrent.locks.ReentrantLock

// other globals
val ReentrantLock lock = new ReentrantLock

// other rules
rule "gPresent changed"
when
        Item gPresent received update
then
        try {
                lock.lock
                // your code goes here
        }
        catch(Throwable t){
                logError("Presence", "Error processing BTPresence update: " + t)
        }
        finally {
                lock.unlock
        }
end

That worked! It never accured to me that it was creating two timers! It should have been obvious with the double log especially since I was seeing the timer being canceled. Thanks for the help, I’m new to openhab but so far this is a great community! Now I know how to create locks too!

Since were on the subject any idea why it would be running twice in the first place, wondering if there’s something wrong with the code itself cause it to loop?

It is a side effect of how the state of a Group Item gets calculated. The short of it is that your rule will triggered n times per item update where n is the number of items they are a member of the group.

You are not doing anything wrong. I should have had the lock there in the first place.

I am trying to use your code. However, i noticed that the “gPresent changed” is never trigged. Any idea?

openhab.log does not show the logInfo “Here”

2017-08-22 19:10:07.074 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'default.items'

events.log shows

2017-08-22 19:10:39.364 [ItemStateChangedEvent     ] - S_V_JohnPhoneIP changed from OFF to ON

Item file

Group gPresent     "Present group" <present>
Group gJohnPresent "John Present" <present>

Switch Present "Someone is Present" <present> // turns off 5 minutes after everyone leaves
Switch  S_V_JohnPhoneIP <network>  (gJohnPresent,gPresent) {channel="network:device:d12435eb:online"}

Rules

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.ReentrantLock
val ReentrantLock lock = new ReentrantLock ()

var Timer presenceTimer = null




rule "gPresent changed"
when
        Item gPresent received update
then
                logInfo("mydata", "HERE") // log variable
try {

        lock.lock
                // your code goes here
                val isPresent = gPresent.allMembers.filter(s | s.state == ON).size > 0
                logInfo("mydata", "#" + isPresent + "#") // log variable

                // Someone came home
                if(isPresent && Present.state != ON) {
                        logInfo("Presence", "Someone is home")
                                if(presenceTimer != null) {
                                        presenceTimer.cancel
                                                presenceTimer = null
                                }
                        Present.sendCommand(ON)
                }

        // Everyone is gone
                else if(!isPresent && Present.state != OFF) {
                        logInfo("Presence", "No one is home, setting timer")
                                if(presenceTimer != null) {
                                        presenceTimer.cancel
                                }
                        presenceTimer = createTimer(now.plusMinutes(5), [|
                                        logInfo("Presence", "No one is home after five minutes")
                                        if(Present.state != OFF) Present.sendCommand(OFF)
                        ])
                }


}
       catch(Throwable t){
                logError("Presence", "Error processing BTPresence update: " + t)
        }
        finally {
                lock.unlock
        }
end

I think that this is due to the new way that Groups work now:

Try to use a type (and optionally a function) for your Group definition.
Something like:

Group:Switch gPresent     "Present group" <present>

http://docs.openhab.org/configuration/items.html#group-types