Randomized Arrival/Departure Announcement

As I finish my move to HA, I’m trying to recreate a WebCoRe piston that I use that announces a random welcome/departure message based on who comes home/leaves.

This by itself is fairly straight forward using individual presence as a trigger, a function node to insert the name and randomize the message and then make the announcement after the entry door closes.

Where I’m getting stuck is if both the wife and I come home together. I don’t want to make 2 individual announcements, I want to make a single announcement that combines both of our names. So I guess what I’m looking for is if 2 individual presence triggers have happened within X amount of time of each other.

For fun, here’s the piston I have in WebCoRe that I’m trying to recreate

Thinking out loud to myself I might be able to do this somehow using data.timeSinceChangedMs but then if the second person arrives within this time window would need to cancel the first announcement to prevent it from making 2.

I guess it all depends on your sensor and trigger setup. I’d probably have the door opening as the trigger and check who is home at that point of time. Not sure if that would work for your presence sensors though.

I don’t believe that would work out because if the wife was home and I arrive then the check would show both home and would announce both.

The WebCoRe version I’m using uses arriving home as a trigger and uses a “wait for condition” of the door being closed plus a few extra seconds before making the announcement.

In my head I’m picturing this:
Presence trigger, delay 30s-1min, check if the other has arrived home within that time period, if no send it to a singular random message, if yes send it to a multiple random message, wait for door to become closed, delay 2-5s, make announcement.

But the issue I’m picturing with this method is that when the second presence trigger fires, it’ll queue up a second message

Well, there was a custom component for this case that enhances the state of the presence sensor. But you could deal with that also yourself. If you set at time of arrival a state to: arrived, and that switches to home after a certain time. Then you can check against arrived vs home.
Anyway there are always lots of different ways to achieve the same result. To check against your second triggering you could employ a trafficlight node. With that you could for example stop a second triggering if you do not want that.

Here’s what I’ve got. Haven’t had the opportunity to test it live yet, but it works in testing using inject nodes to simulate arrival/departures. Multiple injects still only results in 1 spoken message which was my main concern.

For arrival, triggered by either presence device, the get entities node matches presence sensors that are on who’s states have updated in the last 5min. The function node takes the friendly names of the devices and strips it to just our first names, adds is or are depending on how many people, and then builds a randomized message. Wait for door to open and then close (Is there a way to do this with a single node?) and then finally speaks the message.
For departure it’s the same idea but no waiting on the door because the person will have already been gone so I just put a 5s delay for now but may increase to help prevent it from firing multiple times if we drive in and out of the geofence on our way out.

[{"id":"cd69ed99.27fa3","type":"ha-get-entities","z":"d614ccff.478af","server":"94fdcfdf.a00b","name":"","rules":[{"property":"entity_id","logic":"includes","value":"binary_sensor.mike_presence_presence, binary_sensor.ashley_presence_presence","valueType":"str"},{"property":"state","logic":"is","value":"on","valueType":"str"},{"property":"timeSinceChangedMs","logic":"lt","value":"300000","valueType":"num"}],"output_type":"split","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":310,"y":2920,"wires":[["463dcd68.92c6f4"]]},{"id":"463dcd68.92c6f4","type":"template","z":"d614ccff.478af","name":"Format Names","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{payload.attributes.friendly_name}}","output":"str","x":480,"y":2920,"wires":[["55d03dbd.b2e1b4"]]},{"id":"55d03dbd.b2e1b4","type":"join","z":"d614ccff.478af","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":", ","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":630,"y":2920,"wires":[["313faa46.736746"]]},{"id":"313faa46.736746","type":"function","z":"d614ccff.478af","name":"Random message","func":"var device=msg.payload\nmsg.payload = {};\nmsg.payload.domain = {};\nmsg.payload.service = {};\nmsg.payload.data = {};\nmsg.payload.data.data = {};\nmsg.payload.data.data.type = {};\nmsg.payload.data.message = {};\n\nif (device == 'Mike - Presence presence') {\n    var name = 'Mike';\n    var isAre = 'is';\n} else if (device === 'Ashley - Presence presence') {\n    var name = 'Ashley';\n    var isAre = 'is';\n} else if (device == 'Mike - Presence presence, Ashley - Presence presence' || device == 'Ashley - Presence presence, Mike - Presence presence') {\n    var name ='Mike, Ashley and Hailey';\n    var isAre = 'are';\n}\n\nvar messageOpening = [\n  \"Welcome Home\",\n  \"How you doing\",\n  \"What's up\",\n  \"Long time no see\",\n  \"Hey\",\n  \"Nice to see you\",\n  \"Look who's home, it's\",\n  \"Nice to have you back\",\n  \"Howdy do\",\n  \"What's going on\",\n  \"How is everything\",\n  \"It's feels like ages since I've seen you\",\n  \"Where have you been hiding\",\n  \"How's it hanging\",\n  \"What's cookin\",\n  \"What's shakin\",\n  \"Greetings and salutations\",\n  \"How goes it\",\n  \"What's happening\",\n  \"How goes it\",\n  \"Roll out the red carpet for\"\n];\n\nvar messageClosing = [\n  \"in the house! Ooo oooo\",\n  \"here.\",\n  \"home.\",\n  \"joining the party\",\n  \"crashing the party\",\n  \"gracing us with their presence\",\n  \"dropping anchor\",\n  \"dropping in\",\n  \"making the scene\",\n  \"barging in\",\n  \"showing up\",\n  \"blowing in\",\n  \"appearing\",\n  \"breezing in\",\n  \"rolling in\",\n  \"popping in\",\n  \"finally home\"\n];\n\n\nvar randomOpening = messageOpening[Math.floor(Math.random()*messageOpening.length)];\nvar randomClosing = messageClosing[Math.floor(Math.random()*messageClosing.length)];\nvar message = randomOpening + \" \" + name + \".\" +  \" \" + name +  \" \" + isAre +  \" \" + randomClosing\n\nmsg.payload.domain = \"notify\"\nmsg.payload.service = \"alexa_media_front_entrance_echo_dot\"\nmsg.payload.data.data.type = \"announce\"\nmsg.payload.data.message = message\n\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":2920,"wires":[["fdd42ad2.a90ec8"]]},{"id":"a5b8bb83.088348","type":"api-call-service","z":"d614ccff.478af","name":"Speak Message","server":"94fdcfdf.a00b","version":1,"debugenabled":false,"service_domain":"notify","service":"alexa_media_front_entrance_echo_dot","entityId":"","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1300,"y":2920,"wires":[[]]},{"id":"fdd42ad2.a90ec8","type":"ha-wait-until","z":"d614ccff.478af","name":"Door Opens","server":"94fdcfdf.a00b","outputs":2,"entityId":"binary_sensor.garage_door","entityIdFilterType":"exact","property":"state","comparator":"is","value":"on","valueType":"str","timeout":"5","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":true,"blockInputOverrides":true,"x":970,"y":2920,"wires":[["c2c00135.96b8d"],[]]},{"id":"c2c00135.96b8d","type":"ha-wait-until","z":"d614ccff.478af","name":"Door Closes","server":"94fdcfdf.a00b","outputs":2,"entityId":"binary_sensor.garage_door","entityIdFilterType":"exact","property":"state","comparator":"is","value":"off","valueType":"str","timeout":"5","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":true,"blockInputOverrides":true,"x":1130,"y":2920,"wires":[["a5b8bb83.088348"],[]]},{"id":"21d72e3e.645d92","type":"ha-get-entities","z":"d614ccff.478af","server":"94fdcfdf.a00b","name":"","rules":[{"property":"entity_id","logic":"includes","value":"binary_sensor.mike_presence_presence, binary_sensor.ashley_presence_presence","valueType":"str"},{"property":"state","logic":"is","value":"off","valueType":"str"},{"property":"timeSinceChangedMs","logic":"lt","value":"300000","valueType":"num"}],"output_type":"split","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":310,"y":3000,"wires":[["23a17f82.48a09"]]},{"id":"23a17f82.48a09","type":"template","z":"d614ccff.478af","name":"Format Names","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{payload.attributes.friendly_name}}","output":"str","x":480,"y":3000,"wires":[["ede567df.388938"]]},{"id":"ede567df.388938","type":"join","z":"d614ccff.478af","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":", ","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":630,"y":3000,"wires":[["dc985425.dbbe68"]]},{"id":"dc985425.dbbe68","type":"function","z":"d614ccff.478af","name":"Random message","func":"var device=msg.payload\nmsg.payload = {};\nmsg.payload.domain = {};\nmsg.payload.service = {};\nmsg.payload.data = {};\nmsg.payload.data.data = {};\nmsg.payload.data.data.type = {};\nmsg.payload.data.message = {};\n\nif (device == 'Mike - Presence presence') {\n    var name = 'Mike';\n    var hasHave = 'has';\n} else if (device === 'Ashley - Presence presence') {\n    var name = 'Ashley';\n    var hasHave = 'has';\n} else if (device == 'Mike - Presence presence, Ashley - Presence presence' || device == 'Ashley - Presence presence, Mike - Presence presence') {\n    var name ='Mike, Ashley and Hailey';\n    var hasHave = 'have';\n}\n\nvar messageOpening = [\n  \"Goodbye\",\n  \"See you later\",\n  \"Bye\",\n  \"Live long and prosper\",\n  \"Farewell\",\n  \"Hope you have a great time\",\n  \"Take care\",\n  \"Smell you later\",\n  \"Adios\",\n  \"Caio\",\n  \"Au revoir\",\n  \"Sayonara\",\n  \"Ta Ta for now\",\n  \"Catch you later\",\n  \"To-da-loo\",\n  \"See you in the funny papers\",\n  \"Toodles\",\n  \"Godspeed\",\n  \"So long\",\n  \"Cheerio\",\n  \"Arrivederci\"\n];\n\nvar messageClosing = [\n  \"left the building\",\n  \"gone out\",\n  \"popped out\",\n  \"left\",\n  \"buggered off\",\n  \"departed\",\n  \"split\",\n  \"vamoosed\",\n  \"high tailed it\",\n  \"escaped\",\n  \"eight-six'd it\",\n  \"bid farewell\",\n  \"flaked off\",\n  \"took a hike\",\n  \"withdrawn\",\n  \"exited\",\n  \"vanished\",\n  \"disappeared\",\n  \"evacuated\",\n  \"retreated\",\n  \"taken their leave\"\n];\n\n\nvar randomOpening = messageOpening[Math.floor(Math.random()*messageOpening.length)];\nvar randomClosing = messageClosing[Math.floor(Math.random()*messageClosing.length)];\nvar message = randomOpening + \" \" + name + \".\" +  \" \" + name +  \" \" + hasHave +  \" \" + randomClosing\n\nmsg.payload.domain = \"notify\"\nmsg.payload.service = \"alexa_media_front_entrance_echo_dot\"\nmsg.payload.data.data.type = \"announce\"\nmsg.payload.data.message = message\n\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":3000,"wires":[["6652ec19.c20604"]]},{"id":"b65ea10e.90b65","type":"api-call-service","z":"d614ccff.478af","name":"Speak Message","server":"94fdcfdf.a00b","version":1,"debugenabled":false,"service_domain":"notify","service":"alexa_media_front_entrance_echo_dot","entityId":"","data":"","dataType":"json","mergecontext":"","output_location":"payload","output_location_type":"msg","mustacheAltTags":false,"x":1120,"y":3000,"wires":[[]]},{"id":"6652ec19.c20604","type":"delay","z":"d614ccff.478af","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":960,"y":3000,"wires":[["b65ea10e.90b65"]]},{"id":"88615cb1.52921","type":"server-state-changed","z":"d614ccff.478af","name":"Mike Presence","server":"94fdcfdf.a00b","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.mike_presence_presence","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":100,"y":2920,"wires":[["cd69ed99.27fa3"],["21d72e3e.645d92"]]},{"id":"4f15dd42.fd1fc4","type":"server-state-changed","z":"d614ccff.478af","name":"Ashley Presence","server":"94fdcfdf.a00b","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.ashley_presence_presence","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"x":100,"y":3000,"wires":[["cd69ed99.27fa3"],["21d72e3e.645d92"]]},{"id":"7b483db6.6cf044","type":"comment","z":"d614ccff.478af","name":"Announce arrival and departure","info":"","x":150,"y":2860,"wires":[]},{"id":"94fdcfdf.a00b","type":"server","z":"","name":"Home Assistant","addon":true}]

Any updates on this? Looking for the same thing but have no idea how to build it in Node-Red. Just wondering if you got it working the way you wanted.

Thanks

The above worked great. The only change I made to it was adding a time range node so it only announces between 7:30am and 10pm.

1 Like