Home occupancy based on motion sensors, device trackers and door contacts in Node-RED

Hi guys,
I am trying to implement a Home occupancy input boolean based on several motion sensors, device trackers and door contacts.
I have created separate groups for each (motion, contact, person) to get on/off (home/not_home) state if any of them are on, or all of them are off.
Now I would like to make a flow in Node-RED which would determine if house is occupied or not.

House occupied: Any of the motion sensors triggered in last 30 minutes, any of persons home, any of the door contacts were open in last 30 minutes.

House not occupied: same as above in reverse.

Is there a more clever way how to achieve this than having 3 separate flows for each of the trigger groups changing state, then checking the two other groups’ state and determining the ‘occupied’ state and having 3 other flows for determining the ‘not occupied’ state? Or would it be better just to write a custom javascript function, feeding all three trigger groups to the input and doing the rest there? I can program in Javascript, I would just have to read the docs on reading the states (or variables if I save the state to the flow. variable), but I would like to use more Node-RED style approach.

I typically just use the Person integration with multiple device trackers (Life360, mobile app and nmap scanning) and put each person into a group.

You could do a simple flow to set your input boolean though. Each group of sensors trigger on state change. When state hasn’t changed for 30 minutes (trigger nodes), it sets the input boolean to whatever state you want. If a sensor trips during that time, it resets the trigger. This is just a pseudo flow and can be fleshed out as needed.

To answer your question, you could just use 3 events: state nodes, one for each group, and have them feed a single function node. This snippet is for events that happen when someone gets home.

This is the grouping where everyone has left/arrived.

2 Likes

Thank you for your answer and examples. I am using the same flow you showed in the first picture for my motion triggered lights, to add some delay before they turn off. However I think in this case it wouldn’t work, since for example motion sensors could switch to off state, triggering the 30 minut delay, and if there is no change, it would set the occupancy mode to “not occupied” but still there could be people in the house (detected by device_tracker) whose state wasn’t changed, thus not resetting the trigger node. I guess I will have to connect the output of the trigger to ‘current-state’ node to check the state of other devices. It would be even better if it would detect the last time it was changed. Not sure if the ‘current-state’ node outputs the last changed attribute or not. I will try to come up with something before jumping to the single function node and post the results.

You can. It’s in the msg.data object:

This is exactly why I don’t rely on motion or contact sensors for presence management. I often sit at my desk (I work from home) and may not move enough for my motion sensors to trip within 30 minutes (typically when I’m reading or on the phone). For me, using the 3 presence types attached to the person has worked flawlessly.

Awesome, thank you, I will give it a go when I get home. Then I suppose I could use the ‘timecheck’ node (altough there is not much of a description about how it works), that should tell me if the time difference is more than 30 minutes.

1 Like

Honestly, I’d do it in a function node. Much simpler and much more control. Just convert the timestamps to Unix time and do a subtraction. You could also use a simple switch node with a JSONata expression, but for me, function nodes are much simpler as I can bang out JS all day long. lol

The time switch node is good for defined time ranges (sunset-sunrise, etc). Duh, time check, not time switch. Meh, more coffee is needed!

1 Like

So in case somebody wants to do something similar, this is what I have come up with so far.
Turning occupancy on is simple, any of the three trigger groups changes to on/home it sets the occupancy to on.

Turning off is a bit more complicated:

  1. Any of the three groups gets triggered (motion off or door contact off or persons not_home).
  2. It proceeds through ‘current-state’ checks to see if all of the groups are off. (I first used HA’s trigger:state node, where I could set the constraints for other groups, but I can’t manually trigger it after the 1minut delay, so the checks are done with ‘current-state’ nodes)
  3. Last current-state node in the sequence ‘Motion Off’, sets the msg.data with last_changed timestamp, this is used by the function.
  4. Function checks if the difference is more than 30 minutes, if it is, it sends the message to the first output, setting the occupancy to off.
  5. If the delay is not more than 30 minutes, it waits for a minute and checks all of the current-state conditions again, as well as the 30 minut delay.

If any of the trigger groups sets to ‘on’ in the meantime, while waiting for the timeout, it stops the flow.


1 Like

HI,

Can you share the code :slight_smile:

Sure, here it is.
It has been slightly modified, but you can probably delete the debounce part, it was used for something else.

[{"id":"ba165f18.a649c","type":"subflow","name":"On HA start","info":"","category":"","in":[],"out":[{"x":480,"y":100,"wires":[{"id":"503bcab3.f198b4","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"503bcab3.f198b4","type":"mqtt in","z":"ba165f18.a649c","name":"","topic":"homeassistant/start","qos":"1","datatype":"auto","broker":"a86136e0.7fe238","nl":false,"rap":false,"inputs":0,"x":280,"y":100,"wires":[[]]},{"id":"a86136e0.7fe238","type":"mqtt-broker","name":"Local","broker":"127.0.0.1","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"f813e24b.87123","type":"server-state-changed","z":"81630af4.767858","name":"Door Contacts","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.contact_sensors_all","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":110,"y":80,"wires":[["9e4f26d8.7bd9a8"]]},{"id":"7d481e90.ede3f","type":"server-state-changed","z":"81630af4.767858","name":"Motion Detectors","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.indoor_motion_detectors","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":140,"wires":[["9e4f26d8.7bd9a8"],[]]},{"id":"9886867d.55a778","type":"server-state-changed","z":"81630af4.767858","name":"Persons","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.persons_home","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"home","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":100,"y":200,"wires":[["9e4f26d8.7bd9a8"],[]]},{"id":"9e4f26d8.7bd9a8","type":"api-call-service","z":"81630af4.767858","name":"Home Occupied","server":"4200f2e6.56ab5c","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.home_occupied"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":460,"y":140,"wires":[[]]},{"id":"855b0800.cde0f8","type":"api-current-state","z":"81630af4.767858","name":"Motion Off","server":"4200f2e6.56ab5c","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"group.indoor_motion_detectors","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":790,"y":540,"wires":[["cc21829f.44779"],[]]},{"id":"cc21829f.44779","type":"function","z":"81630af4.767858","name":"Last Changed Time Check","func":"var last_changed = new Date(msg.data.last_changed);\nvar now = new Date();\n\nvar diff = now - last_changed;\n\nvar diff_minutes = diff / 60000\n\nif (diff_minutes > 15) {\n    return [msg, null];\n} else {\n    return [null, msg];\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":590,"y":600,"wires":[["4d26b7c7.894ac8"],["5ca88dc4.32d314"]]},{"id":"5ca88dc4.32d314","type":"delay","z":"81630af4.767858","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":320,"y":680,"wires":[["4fe65f17.27d94"]]},{"id":"4199b8b7.a95268","type":"api-current-state","z":"81630af4.767858","name":"Door Contacts Off","server":"4200f2e6.56ab5c","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"group.contact_sensors_all","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":570,"y":460,"wires":[[],[]]},{"id":"58e75fe4.b458f","type":"server-state-changed","z":"81630af4.767858","name":"Door Contacts","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.contact_sensors_all","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":110,"y":500,"wires":[["4fe65f17.27d94"],[]]},{"id":"713fa8d1.1146a8","type":"server-state-changed","z":"81630af4.767858","name":"Motion Detectors","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.indoor_motion_detectors","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":560,"wires":[["4fe65f17.27d94"],[]]},{"id":"5270c4f7.fddd9c","type":"server-state-changed","z":"81630af4.767858","name":"Persons","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.persons_home","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"not_home","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"","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":100,"y":620,"wires":[["4fe65f17.27d94"],[]]},{"id":"4fe65f17.27d94","type":"api-current-state","z":"81630af4.767858","name":"Persons Not Home","server":"4200f2e6.56ab5c","version":3,"outputs":2,"halt_if":"not_home","halt_if_type":"str","halt_if_compare":"is","entity_id":"group.persons_home","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":370,"y":540,"wires":[["3d25c888.fff348"],[]]},{"id":"4d26b7c7.894ac8","type":"api-call-service","z":"81630af4.767858","name":"Home Not Occupied","server":"4200f2e6.56ab5c","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.home_occupied"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1000,"y":620,"wires":[[]]},{"id":"d78e4de9.c1c37","type":"inject","z":"81630af4.767858","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":700,"wires":[["4fe65f17.27d94"]]},{"id":"7fe883b4.82d53c","type":"comment","z":"81630af4.767858","name":"Set Home Occupied mode","info":"","x":150,"y":40,"wires":[]},{"id":"90b8f5e4.e0cfb8","type":"comment","z":"81630af4.767858","name":"Set Home NOT Occupied mode","info":"","x":170,"y":440,"wires":[]},{"id":"e4a17fb9.09731","type":"subflow:ba165f18.a649c","z":"81630af4.767858","name":"","x":110,"y":320,"wires":[["94b4845d.8cc7d8","fc9d76f6.9ebe78"]]},{"id":"7d13aaa7.b2c3e4","type":"api-current-state","z":"81630af4.767858","name":"","server":"4200f2e6.56ab5c","version":3,"outputs":2,"halt_if":"home","halt_if_type":"str","halt_if_compare":"is","entity_id":"group.persons_home","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":520,"y":320,"wires":[["9e4f26d8.7bd9a8"],[]]},{"id":"94b4845d.8cc7d8","type":"change","z":"81630af4.767858","name":"","rules":[{"t":"set","p":"home_occupied_debounce","pt":"global","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":380,"wires":[["9568f5ab.9f3cb8"]]},{"id":"9568f5ab.9f3cb8","type":"delay","z":"81630af4.767858","name":"","pauseType":"delay","timeout":"30","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":640,"y":380,"wires":[["4c8e581d.ab1a58"]]},{"id":"4c8e581d.ab1a58","type":"change","z":"81630af4.767858","name":"","rules":[{"t":"set","p":"home_occupied_debounce","pt":"global","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":890,"y":380,"wires":[[]]},{"id":"fc9d76f6.9ebe78","type":"delay","z":"81630af4.767858","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":280,"y":320,"wires":[["7d13aaa7.b2c3e4"]]},{"id":"3d25c888.fff348","type":"api-current-state","z":"81630af4.767858","name":"No Guests Home","server":"4200f2e6.56ab5c","version":3,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.guests_home","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":590,"y":540,"wires":[["855b0800.cde0f8"],[]]},{"id":"98be984.5e3e968","type":"server-state-changed","z":"81630af4.767858","name":"Persons all","server":"4200f2e6.56ab5c","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"group.persons_all","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"home","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":100,"y":260,"wires":[["9e4f26d8.7bd9a8"],[]]},{"id":"4200f2e6.56ab5c","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":false,"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}]
1 Like