How to Only respond to 2nd message (within time window)

It looks like 'node-red-contrib-join-wait' could do this but I do not need to join messages from different paths; and I want to be sure there is no simpler way.

I have a motion sensor and I do not want a light to trigger on the first motion-message; only when it receives a second motion message within a couple of minutes.
I want it a little less 'triggerhappy'.

I was trying out some combos with delay and the powerful 'trigger' nodes, but it does not quite do what I want. I need this:

  • if a message arrives, delay it and start a timer;
  • if a second message arrives within the timer window, forward it immediately;
  • if the timer expires, the node resets
  • if a reset arrives, the node reset.

Try something like this, to reset set flow.time to 0

[{"id":"dc4124a4.3d357","type":"inject","z":"8d22ae29.7df6d","name":"","props":[{"p":"payload"},{"p":"delay","v":"3000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":140,"y":2180,"wires":[["6577879e.0f24"]]},{"id":"6577879e.0f24","type":"switch","z":"8d22ae29.7df6d","name":"","property":"time","propertyType":"flow","rules":[{"t":"jsonata_exp","v":"$millis() - $flowContext(\"time\") < delay","vt":"jsonata"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":270,"y":2180,"wires":[["c62865e.8bb9798"],["9a405fcf.440928"]]},{"id":"9a405fcf.440928","type":"change","z":"8d22ae29.7df6d","name":"","rules":[{"t":"set","p":"time","pt":"flow","to":"$millis()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":2220,"wires":[[]]},{"id":"c62865e.8bb9798","type":"change","z":"8d22ae29.7df6d","name":"","rules":[{"t":"set","p":"time","pt":"flow","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":2140,"wires":[["dcd8ea98.f4e16"]]},{"id":"dcd8ea98.f4e16","type":"debug","z":"8d22ae29.7df6d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":2080,"wires":[]}]
1 Like

Wow! that's a good trick!
I ended up coding it, after all. Javascript can do exactly what I want.

This outputs the 1st message to the 1st output and the second and all following messages to a second output.
Wait for 10 seconds and it resets - and will block the first message again.
It also resets on a 'reset' message.
It's a kind of debounce but instead of dropping the 2nd message, it drops the 1st.
It called 'I insist' because you need to trigger it twice to get through.

[{"id":"5077248c.5ead5c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"11cda614.9dc72a","type":"inject","z":"5077248c.5ead5c","name":"","props":[{"p":"payload.occupancy","v":"false","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":120,"y":200,"wires":[["aea06469.6ec938"]]},{"id":"251c14fa.54998c","type":"debug","z":"5077248c.5ead5c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":640,"y":220,"wires":[]},{"id":"8a0485e2.5fa118","type":"inject","z":"5077248c.5ead5c","name":"true","props":[{"p":"payload.occupancy","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":280,"wires":[["aea06469.6ec938"]]},{"id":"e11b04c4.e8dd08","type":"inject","z":"5077248c.5ead5c","name":"reset","props":[{"p":"reset","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":140,"wires":[["aea06469.6ec938"]]},{"id":"aea06469.6ec938","type":"function","z":"5077248c.5ead5c","name":"I Insist","func":"// input:\n// messages. \n// Send only 2nd and following message within a given timeframe.\n// reset after 'msg reset'\n// reset if time window expires\n\n// implementation: node keeps track of a state. It can be in either one of \n// two modes: holding or Passthrough.\n// Holding mode: when it receives a message it does not send, but goes to Passthrough mode.\n// Passthrough mode:\n// Every message received will be sent on.\n// After N minutes after the last message, the node will leave Passthrough mode and fall back to Holding mode.\n// every message resets the timer, so it takes N mins without messages to fall back to Holding mode\n\nconst BLOCK=1;\nconst PASSTHROUGH=2;\n\nvar now = Date.now();\nvar startTimer=context.get('starttimer')||now;\nvar mode=context.get('mode')||BLOCK; \n\nnewMsg=[null,null];\n// case: Message has arrived.\n// handle reset message: Go to mode BLOCK\nif (msg.hasOwnProperty('reset') && msg.reset!=false)\n{\n    mode=BLOCK;\n    node.status({fill:\"blue\",shape:\"ring\",text:\"reset. now blocking.\"});\n    newMsg=[null,null];\n}\nelse // regular message, either block or forward it. \n{\n    // check timer for expiration, since we do not have a callback when it expires. First time we check is on this message-event.\n    if (mode==PASSTHROUGH && (now-startTimer) > 10*1000)\n    {\n        // timer expired. kill message and go to mode BLOCK\n        mode=BLOCK;\n    }\n    \n    if (mode==BLOCK)\n    {\n        // discard message. change mode. set timer.\n        mode=PASSTHROUGH;\n        startTimer=now;\n        node.status({fill:\"yellow\",shape:\"ring\",text:\"blocked 1 msg\"});\n        newMsg=[msg,null];\n    }\n    else\n    {\n        // mode is passthrough. \n        // reset timer\n        startTimer=now;\n        node.status({fill:\"green\",shape:\"ring\",text:\"forwarded 1 or more msg\"});\n        newMsg=[null,msg];\n    }\n} \n\ncontext.set('mode', mode);\ncontext.set('starttimer', startTimer);\nreturn(newMsg);","outputs":2,"noerr":0,"initialize":"","finalize":"","x":370,"y":220,"wires":[["251c14fa.54998c"],["251c14fa.54998c"]],"outputLabels":["First message ","Next messages"]}]```
1 Like

Do you need the time to be variable?

If not, what would be wrong with this try:

[{"id":"d8da1b21.0a152","type":"inject","z":"703c61ac.837e1","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1900,"y":670,"wires":[["45dd677b.59b1a8"]]},{"id":"45dd677b.59b1a8","type":"trigger","z":"703c61ac.837e1","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"250","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":2100,"y":670,"wires":[["867ba56d.f744a"]]},{"id":"867ba56d.f744a","type":"debug","z":"703c61ac.837e1","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2310,"y":670,"wires":[]}]

Thanks, something like this has been on my TODO list for too long a time. This gets me most of the way there.

For my AI person detection security system the first detection image is often not the best because the person's head can be cut off when first walking into the camera field of view, since there will be more images a fraction of a second later, the later images are generally "better" to push as MMS text or Email notifications.

This gets me most of the way there, if only one message comes in during the interval I'll send it after the delay, but with multiple messages in the interval, the second or third is generally better.

for something like video though won't the standard trigger node work ? set it to send latest msg.payload as the second message - so the first detection triggers it (don't send anything)- then x frames/seconds later it sends the latest it has (rather than the original)?

I haven't fully investigated the trigger node, but it seems to lack the behavior of sending the original message (image) if a second doesn't come within the interval.

if set to send latest - then yes of course if nothing else arrives it will send the original as that was still the latest to arrive...

The node-red-contrib-queue-gate is doing (I think) what you are looking for.
From https://github.com/drmibell/node-red-contrib-queue-gate:

Save Most Recent Message (since version 1.1.0)

This flow, as noted above, saves the most recent message in the queue and releases it when a trigger , flush , or open command is received. (Note that if the open command is used, the gate will remain open until a queue or default command is received to restore the original mode of operation.) The q-gate is configured with Default State = queueing , Maximum Queue Length = 1 , and Keep Newest Messages = true .

So, if further messages are received, they can replace the old ones and if nothing arrives within the time frame you can flush the queue and you will get what is the most recent message available.

Thanks, lots of new ideas to try. But I'm more comfortable inside function nodes at the moment.