HI,
I did something like this - it’s in Nod Red.
If something can be improved, I would be happy to receive suggestions
[{"id":"c2b4a66de860f695","type":"delay","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":395,"y":100,"wires":[["9a872b97e54b2b76"]],"l":false},{"id":"9a872b97e54b2b76","type":"function","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"Filter family","func":"let family = ['person.yuval', 'person.rozi', 'person.adele', 'person.Assaf'];\n\nif(family.includes(msg.topic)) {\n node.status({fill:\"green\",shape:\"dot\",text:\"Family\"});\n return [msg, null];\n}\nnode.status({fill:\"green\",shape:\"dot\",text:\"Guest\"});\nreturn [null, msg];","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":100,"wires":[["e36da9cea5209e95","18b72a81ce3e0920"],[]],"outputLabels":["Familie","Gäste"]},{"id":"10161e5bf8b68a0c","type":"function","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"Create message","func":"/** CONFIGURATION **/\n\n// These are the persons you want to include in the greeting.\nlet persons_whitelist = ['person.yuval', 'person.rozi', 'person.adele', 'person.Assaf'];\n\n// A random greeting will be played every time.\nlet r_greet = [\"Welcome home, \", \"It's nice to see you, \", \"Welcome back, \", \"Hey \", \"Howdy, \"];\n\n/** Optional: Localization\n * If you create a new localization, you can share it in the comments! **/\nlet l10n = {\n \"is\": \"is\",\n \"are\": \"are\",\n \"and\": \"and\",\n \"xyz_is_also_home\": (others, is_are) => others + \" \" + is_are + \" also at home. \",\n \"since\": \"since\",\n \"just_now\": \"just now\",\n \"one_minute\": \"one minute\",\n \"x_minutes\": (minutes) => minutes + \" minutes\",\n \"one_hour\": \"one hour\",\n \"x_hours\": (hours) => hours + \" hours\",\n \"one_day\": \"one day\",\n \"x_days\": (days) => days + \" days\",\n \"away_text\": (persons_with_time) => smart_join(persons_with_time.map(pwt => pwt[0] + \" left \" + pwt[1] + \" ago\")),\n \"final_text\": (text) => hour >= 0 && hour < 6 ? \"<amazon:effect name='whispered'>\" + text + \"</amazon:effect>\" : text \n}\n\n// // Below you find my template for German.\n// // You may also like my r_greet: let r_greet = [\"Willkommen zu Hause, \", \"Schön, dass du da bist, \", \"Willkommen zurück, \", \"Hallo \", \"Moin, \"];\n// let l10n = {\n// \"is\": \"ist\",\n// \"are\": \"sind\",\n// \"and\": \"und\",\n// \"xyz_is_also_home\": (others, is_are) => others + \" \" + is_are + \" auch zu Hause. \",\n// \"since\": \"seit\",\n// \"just_now\": \"gerade eben\",\n// \"one_minute\": \"einer Minute\",\n// \"x_minutes\": (minutes) => minutes + \" Minuten\",\n// \"one_hour\": \"einer Stunde\",\n// \"x_hours\": (hours) => hours + \" Stunden\",\n// \"one_day\": \"einem Tag\",\n// \"x_days\": (days) => days + \" Tagen\",\n// \"away_text\": (persons_with_time) => smart_join(persons_with_time.map(pwt => pwt[0] + \" ist seit \" + pwt[1])) + \" unterwegs.\",\n// \"final_text\": (text) => hour >= 0 && hour < 6 ? \"<amazon:effect name='whispered'>\" + text + \"</amazon:effect>\" : text \n// }\n\n/** Optional: Relations. Replace a name for a person. E.g.: Call Peter \"Dad\" for the kids */\nlet relations = {\n \"person.celine\": {\n \"person.yuval\": \"Dad\",\n \"person.rozi\": \"Mom\",\n },\n \"person.adele\": {\n \"person.yuval\": \"Dad\",\n \"person.rozi\": \"Mom\",\n }\n}\n\n/** END CONFIGURATION.\n * You don't have to change anything below, but if you have a great idea on\n * how to improve this, please write a comment to share it with us! */\n\n// Join the array to a string separated by commas except the last, which gets replaced by \"and\".\nfunction smart_join(arr) { return arr.join(', ').replace(/, ([^,]*)$/, ' ' + l10n[\"and\"] + ' $1'); }\n\n// convert a Date object into a relative time\nfunction relative_time(time_value) {\n var parsed_date = Date.parse(time_value);\n var relative_to = (arguments.length > 1) ? arguments[1] : new Date();\n var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);\n\n if (delta < 60) {\n return l10n[\"just_now\"];\n } else if(delta < 120) {\n return l10n[\"one_minute\"];\n } else if(delta < (60*60)) {\n return l10n[\"x_minutes\"](parseInt(delta / 60).toString());\n } else if(delta < (120*60)) {\n return l10n[\"one_hour\"];\n } else if(delta < (24*60*60)) {\n return l10n[\"x_hours\"](parseInt(delta / 3600).toString());\n } else if(delta < (48*60*60)) {\n return l10n[\"one_day\"]\n } else {\n return l10n[\"x_days\"](parseInt(delta / 86400).toString());\n }\n}\n\n// The arriver is the person that just came home\nlet arriver = msg.data.entity_id;\nlet arriver_name = msg.data.new_state.attributes.friendly_name;\n\nlet hour = new Date().getHours();\nlet home = [];\nlet away = [];\n\n// Get all persons that are home excluding the arriver\nfor(var i=0; i<msg.payload.length; i++) {\n let current = msg.payload[i];\n if(current.entity_id != arriver) {\n\n // Rewrite names for special relations\n var other_name = (relations[arriver] || {})[current.entity_id] || current.attributes.friendly_name;\n\n if(current.state == \"home\") {\n home.push(other_name);\n } else if(persons_whitelist.includes(current.entity_id)) {\n away.push([other_name, relative_time(current.last_changed), current.last_changed]);\n }\n }\n}\n\n// Sort by away time (untested)\naway = away.sort(a => a[2]);\n\n// Create the message\n\n// Get a random greeting from r_greet\nlet greet = r_greet[Math.floor(Math.random()*r_greet.length)];\nlet is_are = home.length == 1 ? l10n[\"is\"] : l10n[\"are\"];\nlet others = smart_join(home);\n\nlet text = greet + arriver_name + \". \";\n\n// Just tell who is home when there is actually someone home\nif(home.length > 0) {\n text += l10n[\"xyz_is_also_home\"](others, is_are);\n}\n\n// Just tell who is away when there is actually someone away\nif(away.length > 0) {\n text += l10n[\"away_text\"](away);\n}\n\nmsg.payload.data = {\n message: l10n[\"final_text\"](text) \n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1200,"y":140,"wires":[["d46a276ee71342b3"]]},{"id":"011f05c2bdcab253","type":"api-call-service","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"📢 Alexa","server":"ca349d57.bd11e","version":5,"debugenabled":false,"domain":"notify","service":"alexa_media","areaId":[],"deviceId":[],"entityId":[],"data":"{\"data\":{\"type\":\"announce\",\"method\":\"all\"},\"target\":\"media_player.yuval_s_echo_dot_3,media_player.yuval_s_echo_show_8,media_player.computer_echo_show\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1500,"y":140,"wires":[[]]},{"id":"e36da9cea5209e95","type":"api-current-state","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"Front door opened","server":"ca349d57.bd11e","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"binary_sensor.door_main_open_or_close","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","x":550,"y":220,"wires":[["18b72a81ce3e0920"],[]]},{"id":"ca7945ba6a03d41f","type":"server-state-changed","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"Someone comes 🏠","server":"ca349d57.bd11e","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"person\\..*","entityIdType":"regex","outputInitially":false,"stateType":"str","ifState":"home","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":100,"wires":[["c2b4a66de860f695"],[]]},{"id":"18b72a81ce3e0920","type":"ha-get-entities","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"🔎 Get all persons","server":"ca349d57.bd11e","version":1,"rules":[{"property":"entity_id","logic":"is","value":"person\\..*","valueType":"re"}],"outputType":"array","outputEmptyResults":false,"outputLocationType":"msg","outputLocation":"payload","outputResultsCount":1,"x":970,"y":160,"wires":[["10161e5bf8b68a0c"]]},{"id":"013ec97f6e17f0e5","type":"ha-wait-until","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"Front door opened","server":"056e71f70138b09c","version":2,"outputs":2,"entityId":"binary_sensor.door_main_open_or_close","entityIdFilterType":"exact","property":"state","comparator":"is","value":"on","valueType":"str","timeout":"3","timeoutType":"num","timeoutUnits":"milliseconds","checkCurrentState":true,"blockInputOverrides":true,"outputProperties":[],"x":710,"y":100,"wires":[["18b72a81ce3e0920"],[]]},{"id":"0b97712e1b595e99","type":"deduplicate","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"","keyproperty":"payload","expiry":"120","x":1055,"y":100,"wires":[[],[]],"l":false},{"id":"d46a276ee71342b3","type":"time-range-switch","z":"aa3761987da6cf5d","g":"f983b0a0a6657714","name":"07:00-20:30","lat":"32.0167","lon":"34.7667","startTime":"07:00","endTime":"20:30","startOffset":0,"endOffset":0,"x":1370,"y":200,"wires":[["011f05c2bdcab253"],[]]},{"id":"ca349d57.bd11e","type":"server","name":"Home Assistant","addon":true},{"id":"056e71f70138b09c","type":"server","name":"yuval_Home Assistant","version":5,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]