Yes, I've taken something from @jason-lane and also @JasonJoel.
It's a subflow that I've worked up (stolen) from their ideas. It records the number of active motions sensors and will not go inactive until all sensors report inactive.
My long term goal is to have it so that I can change the length of delay off (based on time of day), but have not reached that point yet, mainly because the time of day changes in different rooms (late in the evening in the kitchen, early in the hallway etc).
You will need the unsafe function node. Active (lights on) goes out Output 1, inactive (lights off) Output 2.
[{"id":"41a0d0c3.e686a","type":"change","z":"acbe928e.120e6","name":"stop","rules":[{"t":"set","p":"payload","pt":"msg","to":"stop","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":830,"y":260,"wires":[["b7925a95.e71ca8"]]},{"id":"b7925a95.e71ca8","type":"stoptimer-varidelay","z":"acbe928e.120e6","duration":"time_delay_minutes","durationType":"env","units":"Minute","payloadtype":"num","payloadval":"0","name":"Timer","reporting":"last_minute_seconds","persist":true,"x":970,"y":320,"wires":[["3ad07871.0639e8"],[],[]]},{"id":"3ad07871.0639e8","type":"delay","z":"acbe928e.120e6","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":1210,"y":220,"wires":[["7bf14e2d.695bd"]]},{"id":"7c13f4b6.50f13c","type":"unsafe-function","z":"acbe928e.120e6","name":"All sensors inactive","func":"// Output 1 - Active event\n// Output 2 - Inactive event\n\n// node.warn(\"Processing Motion Event\");\n\nvar motionStatus = null;\nvar activeCount = 0;\n\nvar motionSensors = flow.get(\"motionSensors\");\nvar automationActive = flow.get(\"automationActive\");\n\nif (typeof motionSensors == \"undefined\") {\n // node.warn(\"flow.motionSensors not yet defined. Creating new.\");\n var motionSensors = {};\n}\n\nif (typeof msg.payload != \"undefined\" && msg.payload !== null) {\n var deviceId = msg.payload.deviceId;\n}\n\n\nif (typeof deviceId != \"undefined\") {\n // Receiving a device ID indicates we have received an event, so process it.\n // Note: If we didn't receive an event, the process was triggered by something else (eg. enabling the automation)\n motionStatus = msg.payload.value;\n\n if (typeof motionSensors[deviceId] == \"undefined\") {\n \t// First time seeing this devie, so add it to the list now\n // node.warn(\"Device object doesn't exist\");\n motionSensors[deviceId] = {};\n }\n\n var deviceObject = motionSensors[deviceId];\n\n deviceObject.state = motionStatus;\n deviceObject.displayName = msg.topic;\n\n flow.set(\"motionSensors\", motionSensors);\n \n // If this is an 'active' event, then pass the msg on now and finish here\n if (motionStatus == 'active' ) {\n // node.warn(\"New active event received.\");\n return [msg, null];\n }\n}\n\n\nfor (let [deviceId, values] of Object.entries(motionSensors)) {\n if ( values.state == 'active' ) {\n activeCount++;\n }\n}\n\nif (activeCount > 0) {\n // If any of the sensors are still active, proceed as an 'active' event\n // node.warn(\"Still active.\");\n return [msg, null];\n} else {\n // If all sensors are now inactive, proceed as an 'inactive' event\n // node.warn(\"Status: inactive.\");\n return [null, msg];\n}","outputs":2,"noerr":0,"x":570,"y":300,"wires":[["94445edf.4f075"],["b7925a95.e71ca8"]]},{"id":"94445edf.4f075","type":"switch","z":"acbe928e.120e6","name":"","property":"payload.value","propertyType":"msg","rules":[{"t":"eq","v":"active","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":735,"y":220,"wires":[["41a0d0c3.e686a","3ad07871.0639e8"]],"l":false},{"id":"7bf14e2d.695bd","type":"q-gate","z":"acbe928e.120e6","name":"Gate","controlTopic":"control","defaultState":"open","openCmd":"gate_open","closeCmd":"gate_closed","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"100","keepNewest":false,"qToggle":false,"persist":true,"x":1370,"y":180,"wires":[["2f19a526.a2874a"]]},{"id":"6ad89231.76929c","type":"switch","z":"acbe928e.120e6","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"gate_closed","vt":"str"},{"t":"eq","v":"gate_open","vt":"str"},{"t":"eq","v":"stop","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":280,"y":200,"wires":[["7bf14e2d.695bd","aa8c585e.bae7c8","e549dab9.6467a8"],["7bf14e2d.695bd","e549dab9.6467a8"],["41a0d0c3.e686a"],["e549dab9.6467a8"]]},{"id":"2f19a526.a2874a","type":"switch","z":"acbe928e.120e6","name":"","property":"payload.value","propertyType":"msg","rules":[{"t":"eq","v":"active","vt":"str"},{"t":"eq","v":"inactive","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1510,"y":180,"wires":[[],[]]},{"id":"aa8c585e.bae7c8","type":"link out","z":"acbe928e.120e6","name":"","links":["296a6fee.751ce"],"x":375,"y":140,"wires":[]},{"id":"296a6fee.751ce","type":"link in","z":"acbe928e.120e6","name":"","links":["aa8c585e.bae7c8"],"x":1155,"y":280,"wires":[["d47d10af.6250b"]]},{"id":"d47d10af.6250b","type":"change","z":"acbe928e.120e6","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"Closed","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1215,"y":280,"wires":[["d1228c6d.8cdc7"]],"l":false},{"id":"e549dab9.6467a8","type":"q-gate","z":"acbe928e.120e6","name":"Gate","controlTopic":"control","defaultState":"open","openCmd":"gate_open","closeCmd":"gate_closed","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"100","keepNewest":false,"qToggle":false,"persist":true,"x":410,"y":300,"wires":[["7c13f4b6.50f13c"]]},{"id":"d1228c6d.8cdc7","type":"delay","z":"acbe928e.120e6","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1320,"y":280,"wires":[[]]}]
I try and have all the logic about open gates happen before the motion events come in, so that it's quicker. The open gate is sent from outside the subflow, and can be anything(s) you want (button press, lux, presence - just change the message to: topic: control, payload: gate_open and change the relevant line in the switch. Likewise for gate_closed).
There's currently one environment variable (time_delay_minutes), which can be set to whatever you want for each separate deployment (default currently 10 mins).
Improvements welcome.