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.
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.
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.
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.
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.
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.
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?
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.
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.
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.
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?
@rlkoshak I have a problem here I would like to hear the message in italian, and unfortunately the message changes if the person coming home is male or female
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”
But how to assign a message to a specific name?
// 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
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.