[SOLVED] How to check if two items change in 5 seconds? (Presence detection + alexa)

Hi guys,

I’m creating a rule in order to ask Alexa to say “welcome” if someone is coming back home (means: iphone joining my home wifi).

At the moment I can manage if A or B joins the wifi, Alexa says “welcome home, A”, or “welcome home, B”.

My question is how to manage the case when A + B join the wifi within x seconds, so Alexa should say “welcome home, A and B”.

Any idea?

thanks
Andrea

We could perhaps rephrase that as

When X or Y change state to “home” -
If a timer is already running, do nothing
else
Start a 5-second timer

When the timer expires, it checks to see if X+Y, X only, or Y only are “home” and plays a message.

What happens when X is already home, and Y comes home? Might need to think the requirement a bit more.

2 Likes

Well in facts the flow should be like this:

  • X comes to home and the item changes to ON
  • let’s wait few seconds to see if also someone else changes
  • if no one joins the team, Alexa says “welcome home, X”
  • if Y also joins, Alexa says “welcome home, X and Y”

the same with Y alone, and the others.
Maybe if Y is already at home, Alexa should say “welcome home, X. Y is already at home”

Mmmm … let me try something with the Expire Binding

Andrea

Hi all,

I’m working on this rule. The first part works as expected, but the second not.

rule "Welcome Home"
when
    Item PresenceAndrea_Phone_Home_Unifi_online changed to ON or
    Item PresenceCate_Phone_Home_Unifi_online changed to ON
then
    Echo_Living_Room_TTS_Volume.sendCommand('80')
    if (PresenceAndrea_Phone_Home_Unifi_online.state == OFF || PresenceCate_Phone_Home_Unifi_online.state == OFF) {
        createTimer(now.plusSeconds(5), [ | 
        if (PresenceAndrea_Phone_Home_Unifi_online.state == OFF || PresenceCate_Phone_Home_Unifi_online.state == OFF) {
            if (PresenceAndrea_Phone_Home_Unifi_online.state == ON) {
                Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back Andrea</speak>')
            }
            else if (PresenceCate_Phone_Home_Unifi_online.state == ON) {
                Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back Cate</speak>')
            }
        }
        else {
            Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back, Andrea and Cate</speak>')
        } 
        ])
    }
    else {
        if (PresenceAndrea_Phone_Home_Unifi_online.state == ON) {
            Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back Andrea. Cate is already here.</speak>')
        }
        else if (PresenceCate_Phone_Home_Unifi_online.state == ON) {
            Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back Cate. Andrea is already here</speak>')
        }
    }
    Echo_Living_Room_TTS_Volume.sendCommand('50')
end

Most likely the issue is here:

else {
        if (PresenceAndrea_Phone_Home_Unifi_online.state == ON) {
            Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>Welcome back Andrea. Cate is already here.</speak>')
        }
        else if 

That item will match always, and the second never, because when the rule check the state both items are “ON”.

How to verify only the item just changed? is there a way? Otherwise I should create 2 rules, one for each Andrea/Cate presence detected, but it sounds difficult to check if both are coming home together.

Any clue?

Thanks
Andrea

It’s not as easy as it first appears, at all.

Possible approach -
When X or Y (or Z etc.) changes, start a Timer
AND take a snapshot of some kind of the state of all
When timer expires, announce the ones that changed.
(so catching any that changed in the meantime)

A problem with that is that one has already changed before you snapshot.

Perhaps we can use persistence, if you use it.
If you have each Item persisted on every change -

When X or Y or Z changes,
If a timer already running, ignore.
Else start a 5s timer.
When timer expires. get persisted states from 6s ago.
Announce the ones that changed.

1 Like

How about:

  • When X comes home timestampX = currentTime
  • Start 5 second timerCommingHome
  • When Y comes home timestampY = currentTime
  • If timerCommingHome != null then timerCommingHome is already running, otherwise start 5 second timerCommingHome
  • If timer triggers, check if ((currentTime - 5 <= timeStampX) and ((currentTime - 5 <= timeStampY)) then both X and Y arrived at the same time. Else if (currentTime - 5 <= timeStampX) X came home, else Y came home.
1 Like

Not tested or even checked in VSCode. But here is how I would do it.

As you are seeing, this sort of thing is really way more complicated than it looks. I was going to simplify the Rule you already wrote but it will be easier for me to start from scratch (it’s not your code per se, it’s the nature of the problem, but when I see so many nested if else statements I know there is almost always a better way).

Assumptions, X and Y are persisted with an everyChange strategy and members of the Phones Group. Let’s also assume the phone Item names are of the format <person>_Phone (e.g. Andrea_Phone).

var Timer cameHomeTimer = null

rule "Someone came home"
when
    Member of Phones changed to ON
then
    if(cameHomeTimer === null) return; // do nothing if there is already a Timer

    cameHomeTimer = createTimer(now.plusSeconds(5), [ |

        val cameHome = Phones.members.filter[ p | p.state == ON && p.lastUpdate !== null && p.lastUpdate.isAfter(now.minusSeconds(6)) ].map[ name.replace("_Phone", "") ]

        var message = "welcome home, "
        switch(cameHome.size) {
            case 1: message = message + cameHome.get(0)
            case 2: message = message + cameHome.get(0) + " and " + cameHome.get(1)
            default: {
                logError("came home", "Unexpected number of phones: " + cameHome.size)
                return;
            }
        }

        Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>"+message+"</speak>')
        cameHomeTimer = null
    ])
end

Theory of operation:

Trigger the Rule whenever any of the Phones changes to ON.

If there is already a Timer running, exit and ignore this event. The fact that both phones came home within 5 seconds will be handled by the Timer.

Since there wasn’t a Timer running, create one.

When the Timer expires, first get a list of all the names who came home in the past 6 seconds. We do this by filtering the members of Phones into a list that only includes those that are ON and their lastUpdate is within the last six seconds. You can use now.minusMillis(5500) to do 5.5 seconds. You want a little bit of slack here to handle timing and latency between when the Rule triggers and the Timer is created. Once you have the list of Items (the filter) convert that list to just the names of the people by calling .name and stripping out the “_Phone” part of the name to get the name of the person (the map).

Now construct the message to speak based on the number of names that we reduced everything down to on the previous line. This could be made generic but dealing with the commas and “and” is a pain.

Finally, send the command to speak the message and set the timer to null.

4 Likes

Wow,

this is definitely out of my coding skills :slight_smile: thanks for sharing.

If I well understood, I can add more than 2 phone numbers (eg. my children when they will have a phone, of course :wink: ) in the cameHome.size (case 3 …)

But using 6 secs I collect all phone active in my wifi area, after and before the time X or Y joins the network. I mean, what I was looking for is:

  • if X and Y join together, says “welcome, X and Y”
  • if X joins, but Y is already at home: “welcome X, Y is already at home”
  • if X joins, and Y is out, “welcome X”

So maybe I need two buckets … one for the new joining, and one for the phones already there.

Does it make any sense?

Andrea

Yes but the part that constructs the message will be a bit more of a pain. You would replace the switch statement with something like:

        var message = "welcome home, "
        switch(cameHome.size) {
            case 0: {
                logError("came home", "Unexpected number of phones: " + cameHome.size)
                return;
            }
            case 1: message = message + cameHome.get(0)
            case 2: message = message + cameHome.get(0) + " and " + cameHome.get(1)
            default {
                message = message + cameHome.members.reduce[ list, name | list + ", " + name ]
                val ind = message.lastIndexOf(",")
                message = new StringBuilder(str).replace(ind, ind+1," and").toString();
            }
        }

If there are more than two names in the list, it inserts a comma between them in the message and then adds an “and” after the last one. Hope you like the Oxford Comma. :wink:

The code above does exactly that.

First realize that it will never happen that both phones join the network at the exact same moment. So you need to have a time buffer within which if both phones change to ON they are considered to arrive at the same time. The above uses a 6 second time buffer. What ever the time buffer is needs to be slightly longer than the amount of time between when your phone arrives home and the Alexa says the welcome message.

So if both phones change to ON within six seconds it will say “welcome home, X and Y”. If only one changes to ON (meaning the other one remains OFF or it was already ON) it will say “welcome home, X”.

You have to decide how important this one is for you. It complicates the code tremendously. And I don’t have time to code it up for you so it will be an exercise left to the student. You would use the same general approach as above. After the switch statement, you need to filter Phones for only those that are ON and lastUpdate is before 6 seconds. Then construct the list of names similar to the existing switch statement as a template and append them to message.

Good luck!

1 Like

I like the timestamp approach, avoids unnecessary persistence. Timestamps often find use for other purposes too.

1 Like

Fascinating, thanks all for your support here.

Another point I would like to raise. If X is already at home, but his/her phone is switched off due to battery dead … then the phone will come up, but in that case X is already at home from a while, so makes no sense to have the welcome message, right?

:slight_smile:

Don’t quite follow?
You mean, you would like to know if someone is home even when their phone is off.
There is lots about this on the forum, search “presence detection”, where you can augment phone detection with smart use of other detectors - motion, doors, etc. - to work out who is around.

1 Like

If you have sensors on your outside doors then you could apply the ‘Wasp in a box’ algorithm. If the phone has been on while entering the house and since then none of the outside doors have opened then regardless of whether the phone has been switched off (for whatever reason), you can be sure that no one has left the house (the wasp is still in the box) so when the phone switches back on then you could prevent the welcome message from being played.

1 Like

Normally I would agree, but it would greatly complicate the code which is why I went with persistence.

If the only information you have to tell whether you are arriving home or not is when the phone joins the network then you have no choice. You will need to add additional sensors/data to determine when the phone leaves versus turns off.

1 Like

Considering that the phone can run out of energy most likely

  • at home
  • outdoor, but not when I leave my home

And considering also that I can use:

  • the Unifi Binding for understanding when the phone is joining the wifi network
  • IFTTT using geofence for presence detection (approaching the house)
  • in case, also zwave sensors to detect presence when someone uses my doors

I should use:

A) the phone for usual presence detection -> as above
B) if the phone runs out of energy at home (and I’m already at home), then switches back on, if IFTTT is saying “at home” (and of course Unifi) and none of the outside doors have been opened, I can presume the phone is at home -> no welcome message
C) if the phone runs out of energy outdoor (eg. when coming back home), I come back and the outside doors will trigger a generic welcome message. Then the phone switches back on at home, Unifi will say “at home”. In that case IFTTT makes no sense, as it will be of couse “outside” (not triggering the geofence back home with dead battery). -> Unifi can update IFTTT item as “at home”

Does it make any sense?

More complicated the case when the phone is off, and the outside doors sensors trigger after the first time … I’m going outside, or there is another person with phone off coming in?

Andrea

@rlkoshak I have a problem here :slight_smile: I would like to hear the message in italian, and unfortunately the message changes if the person coming home is male or female :frowning:

Any possibility to manage that?

thanks
Andrea

In the case where you know the gender, replace the whole message, instead of just appending to the default.

Mmm … sorry, I didn’t understand.

I know the names of people should use the system. So, for example Andrea is male, Cate is female. So the welcome message should be in the first case “benvenuto”, in the second “benvenuta”, if both “benvenuti” :slight_smile:
But how to assign a message to a specific name?

Let me explain the status:

Group gPhones (gPeople)

Switch Andrea_Phone_Home_Unifi_online "Andrea [MAP(unifi.map):%s]" (gPeople, gPresence, gPhones)
Switch Cate_Phone_Home_Unifi_online "Cate [MAP(unifi.map):%s]" (gPeople, gPresence, gPhones)

influxdb persistence:

Strategies {

    everyMinute : "0 * * * * ?"
    everyHour   : "0 0 * * * ?"
    everyDay    : "0 0 0 * * ?"
    
}

Items {

    gHistory*	: strategy = everyChange, everyHour
    gPresence*	: strategy = everyChange
    gPhones* 	: strategy = everyChange

}

rule modified accordingly:

// Global variables

var Timer cameHomeTimer = null

// Welcome home rule

rule "Someone came home"
when
    Member of gPhones changed to ON
then
    if(cameHomeTimer === null) return; // do nothing if there is already a Timer

    cameHomeTimer = createTimer(now.plusSeconds(5), [ |

        val cameHome = gPhones.members.filter[ p | p.state == ON && p.lastUpdate !== null && p.lastUpdate.isAfter(now.minusMillis(5500)) ].map[ name.replace("_Phone_Home_Unifi_online", "") ]

        var message = "welcome home, "
        switch(cameHome.size) {
            case 0: {
                logError("came home", "Unexpected number of phones: " + cameHome.size)
                return;
            }
            case 1: message = message + cameHome.get(0)
            case 2: message = message + cameHome.get(0) + " and " + cameHome.get(1)
            default: {
                message = message + cameHome.members.reduce[ list, name | list + ", " + name ]
                val ind = message.lastIndexOf(",")
                message = new StringBuilder(str).replace(ind, ind+1," and").toString();
            }
        }

        Echo_Living_Room_TTS.sendCommand('<speak><break time="5s"/>"+message+"</speak>')
        cameHomeTimer = null
    ])
end

nothing happens … :frowning:

The switch statement gets much more complex. Now you need to know not just how many people arrived but who arrived if it’s only one.

case 0: ...
case 1 && cameHome.get(0) == "Andrea": message = "benvenuto Andrea"
case 1 && cameHome.get(0) == "Cate": message = "benvenuta Cate"
default: {
    message = "benvenuti "
    // code to iterate over all the users and build a list of their names, inserting an "and after the last comma"
}

It occurred to me that you don’t need the case 2 because this message is just sent to Alexa so there being a stray comma in the case where there are only two people probably doesn’t matter.

Is gPhones changing to ON?

There is a typo here. It should be if(cameHomeTimer !== null) return;

Add additional logging to see what variables are set to and where the Rule gets to.