Presence Detection - Close to Home

Hi all,

I’ve been using the brilliant icloud binding to detect presence for my wife and I, which has been and still is running flawlessly to date. I’ve ‘borrowed’ a number of different scripts from here and it does what I want it to…

The presence detection is mainly based on @rlkoshak’s very useful Generic Presence Detection rule in that it updates a dummy item variable to ON for when we are home and OFF when we, yes you guessed it, are away.

I then have a number of other rules running from this item changing, (think lights, cameras, thermostat routines all changing depending on if we are home or not).

Now the whole presence detection is run by the icloud binding, thus:

My_iPhone_Home is part of the presence group which when it’s value changes turns on vPresent ON/OFF accordingly, (as per rlkoshak link above for more info).

//icloud My iPhone rule
rule "My iPhone Home"
when
    Item My_iPhone_Location changed
then
    // specify your home location - my home long / lat
    val PointType home_location  = new PointType(new DecimalType(my home latitude value), new DecimalType(my home longitude value))
    val PointType phone_location = My_iPhone_Location.state as PointType
    val int distance = phone_location.distanceFrom(home_location).intValue()
    // home radius geofence to determine home/away (in meters)
    if ( distance < 500) 
    {
        My_iPhone_Home.postUpdate(ON)
        logInfo(logName, "I am at home.")
    } 
    else 
    {
        My_iPhone_Home.postUpdate(OFF)
        logInfo(logName, "I am away.")
    }
end

Simple stuff, and as I said it works fine…now I want to take this one step further and have an additional option of ‘close to home’ rather than just home so I can have a few things set up ready for when I’m home, (ie thermostat warming up, driveway lights already on etc), so I added in a secondary if like so:

//icloud My iPhone rule
rule "My iPhone Home"
when
    Item My_iPhone_Location changed
then
    // specify your home location - my home long / lat
    val PointType home_location  = new PointType(new DecimalType(my home latitude value), new DecimalType(my home longitude value))
    val PointType phone_location = My_iPhone_Location.state as PointType
    val int distance = phone_location.distanceFrom(home_location).intValue()
    // home radius geofence to determine home/away (in meters)
    if ( distance < 500) 
    {
        My_iPhone_Home.postUpdate(ON)
        logInfo(logName, "I am at home.")
    } 
    else 
    {
        My_iPhone_Home.postUpdate(OFF)
        logInfo(logName, "I am away.")
        // Radius of around 3 miles / 5 minutes for extended geofence / close to home
        if ( distance > 500 && distance < 5000 )
        {
        // Publish the current distance for reference
        logInfo(logName, "My distance from home -" + distance + "m")
        sendBroadcastNotification("My distance from home - " + distance + "m")
        vMy_iPhone_Close.postUpdate(ON)
        }
    }
end

So, this extra bit basically increases the radius around the home to be greater than 500m and less than 5000m as I’ve worked out once I get into the 5000m zone its around 5 mins to drive home so time for things to get warming etc, I’ve also got it publishing the distance while I test it all.

The item vMy_iPhone_Close is a simple dummy variable item that I will run other rules off on it’s change…ie if changed to ON then set thermostat to 20degrees and turn on lights if dark for example.

Now, here’s the long winded question part, at the moment, while the rule works and produces the expected logfile / notifications, the logic isn’t quite there as I have a few foibles:

  1. My local supermarket is less than 5000m away so while I’m doing a big shop I get a message every 5 minutes, (the icloud refresh rate), telling me my distance from home and so will be triggering whatever I run off this rule every 5 minutes. If I set the geofence to less than 5000m I run the risk of getting through the zone within 5 minutes, and possibly before the icloud refresh, ie getting no positive result.

  2. The rule currently can’t decide if I’m actually coming or going, just if I’m in the ‘zone’ or not so even when I’m leaving, vMy_iPhone_Close will be ON and then thermostat will warm up etc, I only really want it to run if I’m coming back towards home.

  3. At the moment the 5000m radius gives me definitely one, possibly 2 if it refreshes at the right time, icloud refreshes so I can’t determine if distance from home is reducing or increasing to fix part 2…and if I increase the radius by much more I start including a lot more of my local activities, (ie more than just supermarket), thereby making the rule less useful as I want to, for example, save money and make sure the thermostat isn’t heating an empty house for too long.

So, I’m after any thoughts to help frame what I’m trying to achieve…I’m sure there is a way it might just need some other input to get me thinking in a different way…:+1:

Can I possibly capture the one instance within the extended radius and store it in a variable to ignore repeat instances? Can I maybe extend the radius but have only the first 2 stored and then if second < first I’m moving closer to home etc???

1 Like

I don’t have time for a thorough answer so here are some quick hints.

  1. Set a flag to not alert until you go outside the 5000m geofence. Disable the above Rule when the flag is set essentially so you have to go beyond the 5000m geofence and then come back before the Rule will be allowed to run.

  2. See 1 above. With the flag set it can tell if you are leaving (flag not set) or coming (flag set).

  3. See 1 and 2 above.

I haven’t looked at the actual code yet so this is just an initial suggestion.

You should have a look at the new Homekit options in iOS 12/13.
I switched my presence detection from the iCloud binding to Homekit with Homebridge.

Homekit have two options for geofencing.

  1. The “at home” zone
    -This zone can be used for everyone in your Home
    -This zone can´t be changed and the radius is fixed
  2. Your own zone
    -This zone can only be used by the person that creates the automation
    -This zone has an adjustable radius

I´m using the home zone in 6 automations:

  1. Anyone arrives or leaves home = change an dummy switch
  2. I´m arriving or leaving home = change my dummy switch
  3. My wife is arriving or leaving home = change my wifes dummy switch
    All this can be done on your phone and you just need to add another person once.

When using the zones with radius, you´ve to setup two automations per device/person you want to monitor.
As this is done on the device itself, you don´t need any rules that may fail.
And it´s the most accurate way, because the device itself knows when you arrive or leave in the zone.

Thanks both…

I’ve had a little time to roll this around my subconcious and I’ve had a quick rethink. It’s now coming from a different direction.

I’ve changed it so that I now have a case statement feeding a dummy variable stating one of currently 4 options for my location. I quite like this way as it means it is scalable ie I can add more case options in at a later date. (I’m thinking holiday / abroad etc)

I’ve not tested this in anger but I’d relish any comments so far…

// home location = lat, long
val PointType home_location  = new PointType(new DecimalType(lat), new DecimalType(long))
// My work location = lat, long
val PointType My_work_location  = new PointType(new DecimalType(lat), new DecimalType(long))

//icloud My iPhone rule
rule "My iPhone Location detection"
when
    Item My_iPhone_Location changed
then
    val PointType phone_location = My_iPhone_Location.state as PointType
    val int distance = phone_location.distanceFrom(home_location).intValue()
    val int workdistance = phone_location.distanceFrom(My_work_location).intValue()

    // Calculate the current location for GP
    var GPCurrLoc = "UNKNOWN"
    switch distance {
        case distance < 200:                                GPCurrLoc = "HOME"
        case distance > 200     && case distance < 5000:    GPCurrLoc = "LOCAL"
        case distance > 5000:                               GPCurrLoc = "AWAY"           
        }
    if(workdistance < 200) GPCurrLoc = "WORK"
    
    // Publish the current state
    logInfo(logName, "GP Current Location = " + GPCurrLoc)
    vGPCurrLoc.sendCommand(GPCurrLoc)
    // Publish the current state
    logInfo(logName, "My Current Location = " + MyCurrLoc)
    vMyCurrLoc.sendCommand(MyCurrLoc)
// set home status
rule "do something based on vMyCurrLoc status"    
when 
    Item vMyCurrLoc changed
then
    if vMyCurrLoc.state == "HOME"
    {
        My_iPhone_Home.postUpdate(ON)
    }
    else
    {
        My_iPhone_Home.postUpdate(OFF)
    }
end

The second ‘set home status’ rule isn’t finished as I can now add in other if else statements based on the vMyCurrLoc value to do different things.

I’m hoping to also use the Item.previousstate() command to be able to work out if I’m coming home or not, ie if previous state == away and current state == local then I’m coming home…

Any comments / ways to improve or tidy up? I’ll then tidy up and post a final working rule when it’s tested and working properly…

Here are a few comments.

  • 'longis a reserved word. You cannot use it in your variables. I'm assuming that you will replacelatandlong` with the actual numbers but wanted to raise this in case you have lat and long defined somewhere else.

  • It is possible to put that hanging workdistance if statement as a case in your switch. Switch statements in Rules DSL are weird and behave much more like a bunch of if/else if/else statements than the way switch statements typically work in other langauges. So the following is perfectly permissible.

    var GPCurrLoc = "UNKNOWN"
    switch distance {
        case distance < 200:                                GPCurrLoc = "HOME"
        case distance > 200     && case distance < 5000:    GPCurrLoc = "LOCAL"
        case distance > 5000:                               GPCurrLoc = "AWAY"
        case workdistance < 200:                            GPCurrLoc = "WORK"
        }
  • Because you are using the a changed trigger, you will have a previousState implicit variable defined. So you don’t need persistence and to call MyCurrLoc.previousState(true) to get the previous state.

  • You might need to call .toString on the state to compare it to “HOME”. Also, the parens are required.

    if(vMyCurrLoc.state.toString == “HOME”)…

  • This is a good place where the trinary operator can reduce lines of code:

    My_iPhone_Home.postUpdate(if(vMyCurrLoc.state.toString == “HOME”) ON else OFF)

  • I would consider trying to move the “smarts” of determining whether you are coming or going in “My iPhone Location detection” and then you can eliminate the My_iPhone_Home Item and Rule (and all the rest of these similar Items and Rules) and just rely on the state of vMyCurrLoc to know where you are and what to do.

Thanks for the quick reply!

Yes, long and lat will be the correct longitude and latitude numbers, I just didn’t want to broadcast my home location online :wink:

I didn’t realise I could mix the case parts inside the statement. One question I do have is the switch expression part, what should that expression represent, I’ve always thought it had to be the variable / item etc that you wanted to case against??? ie in this case, distance…

The My_iPhone_Home is a dummy on / off item that is part of the gPresent group which feeds into my modified version of your presence detection so I’ll probably keep it this way but it’s an interesting thought, maybe I could change it up to use the vMyCurrLoc item instead…

That’s how a normal switch statement works but, like I said, switch statements in Rules DSL are weird.

If you are doing equals comparisons for each case, then yes, what you pass to the switch is used.

    switch vTimeOfDay.state.toString{
        case "DAY": // do something
        case "NIGHT": // do something
    }

In all other cases, it doesn’t matter what you pass to the switch. It’s ignored. So it doesn’t really matter in your switch statement above that you are passing in distance because you never actually implicitely use it in the case statements.

Wow, mind blown!

I love coding, it should be the most rigid, straight forward language there is, it’s binary, right or wrong…but no, every language has it’s foibles!

Right, modified it some more:

  1. Put it all into one rule
  2. Trimmed down the logging to only when the state changes
  3. I’ve changed it so that it deals with the “home” state in the first part of an if as it’s the implicit part ofthe statement, if I’m home I want to set the GP_iphone_Home variable to ON to trigger the presence rule, there will never be a time when I’m home but I don’t want to be present…
  4. The rest of the logic is in the else statement and again, the implicit part is first, if I’m not home then I want the GP_iphone_Home to be OFF…the else statement here then deals with the different away states of vGPCurrLoc and I can run different commands from here - at the moment I’m just posting to the logfile for testing.

Thoughts so far?

//icloud GP iPhone rule
rule "GP iPhone Location detection"
when
    Item GP_iPhone_Location changed
then
    val PointType phone_location = GP_iPhone_Location.state as PointType
    val int distance = phone_location.distanceFrom(home_location).intValue()
    val int workdistance = phone_location.distanceFrom(GP_work_location).intValue()

    // Calculate the current location for GP
    var GPCurrLoc = "UNKNOWN"
    switch distance {
        case distance < 200:                                GPCurrLoc = "HOME"
        case distance > 200     && case distance < 5000:    GPCurrLoc = "LOCAL"
        case distance > 5000:                               GPCurrLoc = "AWAY"
        case workdistance < 200:                            GPCurrLoc = "WORK"
        }

    // Publish the current state when it changes
    if(GPCurrLoc != vGPCurrLoc.state.toString)
    {
        logInfo(logName, "GP Current Location = " + GPCurrLoc)
        vGPCurrLoc.sendCommand(GPCurrLoc)
    }

    // Set Home Item based on vGPCurrLoc to set vPresent
    if(vGPCurrLoc.state.toString == "HOME" && GP_iPhone_Home.state != ON) 
    {
        GP_iPhone_Home.postUpdate(ON)
        logInfo(logName, "GP is at home.")
    }
    else
    {
        GP_iPhone_Home.postUpdate(OFF)
            switch vGPCurrLoc.state.toString{
            case "LOCAL":   logInfo(logName, "GP location is LOCAL") // do some other stuff
            case "AWAY":    logInfo(logName, "GP location is AWAY") // do some other stuff
            case "WORK":    logInfo(logName, "GP location is WORK") //do some other stuff
            }      
    }
end

Depending on how many lines of code is represented by // do some other stuff I might move that stuff to other Rules.

rule "GP is at Work"
when
    Item vGPCurrLoc changed to WORK
then
    // do some other stuff
end

You have two things going on here. First figuring out what state you are in (HOME, AWAY, WORK, LOCAL) and then deciding what to do when you transition from one of those states to another. I tend to prefer separating the calculation of the state from the actions that take place when you transition into a new state. It ends up being a little easier to maintain and understand in the long run and if gives you a bit more flexibility to change things (e.g. adding a new state) without risking breaking stuff that already works.

Note that the state of vGPCurrLoc is unlikely to have been updated from the command a few lines prior by the time that the switch statement runs. Just use GPCurrLoc.

Thanks, I agree, I may split the // do some other stuff part out - once I’ve figured out what ‘other stuff’ I need to do and how complex it is.

I also thought that i might need to split out the actions into a new rule, but again I now need to decide what those actions are and how complex they may get…at least I now think I’m at an iteration of the main rule that I’m comfortable with to kick on…

Been playing some more, I have split them out as suggested.

The first rule simply does the job of determining the state of my location ‘vMyCurrLoc’. I like this case statement as it gives me the ability to add locations and also additional distances / states where needed in the future. I can also change the distance for each state if needed.

//icloud My iPhone rule
rule "My iPhone Location detection"
when
    Item My_iPhone_Location changed
then
    val PointType phone_location = My_iPhone_Location.state as PointType
    val int distance = phone_location.distanceFrom(home_location).intValue()
    val int workdistance = phone_location.distanceFrom(My_work_location).intValue()

    // Calculate the current location for Me
    var MyCurrLoc = "UNKNOWN"
    switch distance {
        case distance < 200:                                MyCurrLoc = "HOME"
        case distance > 200     && distance < 5000:         MyCurrLoc = "LOCAL"
        case distance > 5000:                               MyCurrLoc = "AWAY"
        case workdistance < 200:                            MyCurrLoc = "WORK"
        }

    // Publish the current state when it changes
    logInfo(logName, "My Current Location = " + MyCurrLoc)
    vMyCurrLoc.sendCommand(MyCurrLoc)
end

The second rule does the action gubbins, the logic is that if the location is HOME then it sets the item ‘My_iPhone_Home’ to ON which then triggers my presence detection item vPresent to ON, this then in turns triggers a load of other rules.

If I’m not home then I want this item set to OFF, as I’m clearly not home.

In addition I have created a CASE statements the mirror the above rule location states so I can now do different things / set dummy items for each state depending on what I want to do.

For example, when the state is “LOCAL” I can find out if the previous state was AWAY and then if it was it means I’m coming home so it turns the thermostat up to warm the house…

//icloud My iPhone rule
rule "My iPhone Location detection - update based on vMyCurrLoc"
when
    Item vMyCurrLoc changed
then
    // Set Home Item based on vMyCurrLoc to set vPresent
    if(vMyCurrLoc.state.toString == "HOME") 
    {
        My_iPhone_Home.postUpdate(ON)
        logInfo(logName, "My location is HOME")
    }
    else
    {
        My_iPhone_Home.postUpdate(OFF)
        // do some things depending on the state of vMyCurrLoc
            switch vMyCurrLoc.state.toString {
            case "LOCAL":{
                logInfo(logName, "My location is LOCAL") // Post state to log                
                // Turn on Thermostat to warm up
                if(vMyCurrLoc.previousState(true).state.toString == "AWAY")
                    Thermostst_SetPoint.sendCommand(20.5) // turn up thermostat as someone is coming home                
                }

            case "AWAY":{
                logInfo(logName, "My location is AWAY") // Post state to log
                }

            case "WORK":{
                logInfo(logName, "My location is WORK") // Post state to log
                }
            }      
    }
end

I’ve also realised that while I also have to repeat this rule for my wifes phone it also gives me the option to have different location states, distances and ultimately get OH to do different things for each of us if needed for each of our unique locations…

Pleased so far, thanks for the help, and as always comments welcome…always learning…

Hi,

The switch case doesn’t quite work;

    // Calculate the current location for GP
    var GPCurrLoc = "UNKNOWN"
    switch distance {
        case distance < 200:                                GPCurrLoc = "HOME"
        case distance > 200     && distance < 5000:         GPCurrLoc = "LOCAL"
        case distance > 5000:                               GPCurrLoc = "AWAY"
        case workdistance < 200:                            GPCurrLoc = "WORK"
        }

I think I know why…at the moment it gets as far as AWAY and then stops there, it never gets to the WORK case, would I be correct in saying that if the expression part is ignored and it’ essentially an if/else statement that regardless of it being workdistance calculation rather than distance, it still will only get to the third case and then stop…

Should the workdistance case be the first or second case??? I’ll give it a try anyway but as I’m only at work once I can only give it a real life test once a day…

Yes, switch statements run the cases from the top to the bottom and stops on the first case that matches. You will need to order the cases so that the farthest away cases are checked for first and then move in closer to home.

Also notice, in the unlikely event that your distance is exactly 200 or 5000, none of the case statements will be true.

As i already said, have a look at the Homekit possibilities.
You don´t need rules to make the geofencing as you currently try it with the switch case.

You only need one dummy item per zone instead of this complex rule that may fail.
Your dummy changes when entering the zone and you can start whatever rule you want.

Hi,

I will check it out but I’m guessing / assuming that it needs some learning and new bindings etc to set up which will take a bit of time, brain power and effort.

Short term, I want to get this script working as it can be sorted day 1 and working and then longer term I’ll investigate HomeKit etc and see if I can then integrate it into my set up.