Can the Delay node rate limit, but pass first message immediately, and always pass on the latest message?

As I pointed out there are race conditions with that one that mess it up. In particular if two inputs arrive at virtually the same time then they will both get through before the gate closes. Attempting to modify the Trigger one to add the feature will also, I am confident, have race problems.
I suspect the only way to fully satisfy it is to write a function or add the feature to the Delay node.

You are absolutely right @Colin, my suggestion has potential race conditions and attempting to prevent them just moves the race somewhere else.

Isn't @dceejay's solution exactly right though?
Just standard nodes and no fancy control messages (as I recall). Clearly a man with a deep understanding of the language :wink:

I did download @Trying_to_learn's suggestion but it included a node that I don't have installed so I did not test it.

Can this work ? ..

[{"id":"ec06a50fb93ee157","type":"inject","z":"54efb553244c241f","name":"MSG A","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MSG A","payloadType":"str","x":150,"y":2000,"wires":[["0769b75f987235ba"]]},{"id":"863f7eb764566564","type":"inject","z":"54efb553244c241f","name":"MSG B","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MSG B","payloadType":"str","x":150,"y":2060,"wires":[["0769b75f987235ba"]]},{"id":"4943efe9118b71f4","type":"inject","z":"54efb553244c241f","name":"MSG C","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MSG C","payloadType":"str","x":150,"y":2120,"wires":[["0769b75f987235ba"]]},{"id":"60849786e5e2203c","type":"trigger","z":"54efb553244c241f","name":"","op1":"","op2":"flush","op1type":"pay","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":400,"y":2060,"wires":[["e9db8f4747aaf9e2"],["17740c7f7eaee224"]]},{"id":"d8eba91018d2c58b","type":"debug","z":"54efb553244c241f","name":"debug 25","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":880,"y":2060,"wires":[]},{"id":"15cb4c8309340734","type":"change","z":"54efb553244c241f","name":"","rules":[{"t":"set","p":"queue","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":1960,"wires":[[]]},{"id":"dac58007caf9438b","type":"change","z":"54efb553244c241f","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"queue","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":2100,"wires":[["d8eba91018d2c58b"]]},{"id":"e9db8f4747aaf9e2","type":"junction","z":"54efb553244c241f","x":520,"y":2020,"wires":[["3bca06adfd3cf3a0"]]},{"id":"3bca06adfd3cf3a0","type":"junction","z":"54efb553244c241f","x":740,"y":2020,"wires":[["d8eba91018d2c58b"]]},{"id":"17740c7f7eaee224","type":"junction","z":"54efb553244c241f","x":520,"y":2100,"wires":[["dac58007caf9438b"]]},{"id":"0769b75f987235ba","type":"junction","z":"54efb553244c241f","x":280,"y":2060,"wires":[["60849786e5e2203c","15cb4c8309340734"]]}]

Every message is stored in flow Context and also being sent to the Trigger node.
If its the first message it is sent immediately and when the 5 seconds are up the second output of the Trigger node sends a "flush" msg to send whatever is in the queue. You may need a Filter node if you dont want duplicates.

No, if a new message arrives immediately after the queued one is released then that one is passed through immediately, rather than waiting for another 5 secs. As in the title here it is supposed to operate like a Delay node in Rate Limit, but send the latest message after the delay is up, rather than dropping them or queuing them all.

I think that one suffers from the same problem. If a message arrives immediately after the flush then it will get straight through.

If you add a feedback from after the filter to the front of the trigger, then it will block itself again - and not send that message again (loop) due to the filter. There is a v.v.v. small timing gap (the internal node.js loop time) for a message to arrive after the existing message has released and before that feedback happens - that another message could slip through - but it is pretty darn small I think (vs chance of a message turning up any time in the next 5 secs )

Am I right in thinking that after the trigger node sends the message it will service any other nodes that have messages waiting (which might take some time) and then do another pass, which will service the message in the filter node, which will add the message to the queue for the Trigger node. So not that small a window I think. Similar in size to the race windows in other solutions found proposed. It depends how important it is that messages are spaced out correctly. The engineer in me doesn't like to leave such windows open.

Could you set a incoming rate limit of say 50ms to @dceejay flow, then any race condition would need to be over 50ms.

Even then there is a small possibility of hitting it, as we are not running on a real time OS so other things can hog the processor, therefore the 50ms could run down while nothing is going on in node-red. Memory management in nodejs could also do it I think. You can't rely 100% on a real-time delay spacing things out in a non-real time system.

Edit: I think that should be 'non real-time', not non-real time. The time is real enough I think. Or perhaps not, difficult to know :slight_smile:

1 Like

I think this satisfies your requirements:

[{"id":"9ebe08998f198b8e","type":"q-gate","z":"8d7193deb1042133","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":350,"y":1480,"wires":[["f7d527ac279b9c72","fd79c46e7aa9f525"]]},{"id":"8bc4aaf3943c0dbb","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":1320,"wires":[["9ebe08998f198b8e","6f7f783c2cd5f727"]]},{"id":"6f7f783c2cd5f727","type":"delay","z":"8d7193deb1042133","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":680,"y":1320,"wires":[["882ae2a9ad40c8ac"]]},{"id":"882ae2a9ad40c8ac","type":"change","z":"8d7193deb1042133","name":"control + flush","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"flush","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":1320,"wires":[["9ebe08998f198b8e"]]},{"id":"fd79c46e7aa9f525","type":"debug","z":"8d7193deb1042133","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1110,"y":1480,"wires":[]},{"id":"f7d527ac279b9c72","type":"trigger","z":"8d7193deb1042133","name":"","op1":"","op2":"","op1type":"pay","op2type":"payl","duration":"5","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":440,"y":1360,"wires":[[],["6f7f783c2cd5f727"]]}]

q-gate always has the last msg queued and the incoming msg themselves create with the limiter the cycles. And since you want the last msg to be sent, a last cycle is triggered after 5 sec after the last send msg. If only one msg was triggered, the flush does not hurt.

Welcome to the forum @cameo69

That is certainly close. For me it occasionally locks up if I send multiple messages close together but I am not sure why yet.

With @cameo69's flow it is possible to get a message stuck in the queue:

Inject msg A and after 1s inject MSG B. MSG A emerges immediately, MSG B is in the queue.
When MSG B emerges from the queue the 5s trigger starts.
At some point in this 5s window, inject MSG C. It goes in the queue.
The rate limit prevents the flush operation.

The problem can be automated:

[{"id":"1e54316d289588c8","type":"q-gate","z":"864772ef56f82916","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":410,"y":1680,"wires":[["c32004fbc8056531","a18df0789a2f53f1"]]},{"id":"7a1a70977035002f","type":"inject","z":"864772ef56f82916","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MSG A","payloadType":"str","x":110,"y":1600,"wires":[["89b26b6a10d4908f","65df77aa6ce0b183","3898a55a51e5d87f"]]},{"id":"303f91c83bad0fe6","type":"delay","z":"864772ef56f82916","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":600,"y":1560,"wires":[["68efeacf3b849413"]]},{"id":"68efeacf3b849413","type":"change","z":"864772ef56f82916","name":"control + flush","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"flush","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":1560,"wires":[["1e54316d289588c8"]]},{"id":"a18df0789a2f53f1","type":"debug","z":"864772ef56f82916","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":790,"y":1680,"wires":[]},{"id":"c32004fbc8056531","type":"trigger","z":"864772ef56f82916","name":"","op1":"","op2":"","op1type":"pay","op2type":"payl","duration":"5","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":400,"y":1600,"wires":[[],["303f91c83bad0fe6"]]},{"id":"89b26b6a10d4908f","type":"trigger","z":"864772ef56f82916","name":"1s MSG B","op1":"","op2":"MSG B","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":110,"y":1640,"wires":[["65df77aa6ce0b183"]]},{"id":"3898a55a51e5d87f","type":"trigger","z":"864772ef56f82916","name":"11s MSG C","op1":"","op2":"MSG C","op1type":"nul","op2type":"str","duration":"11","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":110,"y":1560,"wires":[["65df77aa6ce0b183"]]},{"id":"39c30c01ab019180","type":"inject","z":"864772ef56f82916","name":"Reset","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"control","payloadType":"str","x":610,"y":1520,"wires":[["68efeacf3b849413"]]},{"id":"65df77aa6ce0b183","type":"junction","z":"864772ef56f82916","x":240,"y":1560,"wires":[["303f91c83bad0fe6","1e54316d289588c8"]]}]

Can this be fixed by tweeking the trigger and rate limit slightly?

Why doesn't the next message coming in flush the gate?

I can't explain it, I can only reproduce it!

But the next message, if one arrives, will indeed release MSG C (I think, not at my pc)

It's a massive 5 second window in which a new message gets stuck. Something is wrong with the combination of trigger and rate limit

I can't have another look till tomorrow. I think the first thing to do is to see if a message comes out of the change node when, after the lockup, another message comes in.

Is a function node an option? Or you really want to do it with a delay node?

Else you could do something like this in a function node:

let delay = 5000; //ms

let lastTime = context.get('lastTime') || 0;
let timeout = context.get('timeout') || 0;

let nowTime = Date.now();

if ((nowTime - lastTime) > delay && !timeout){
    //Send directly
    context.set('lastTime', nowTime);
    context.set('timeout',0);
    return msg;
} else {
    //Create timeout if not existing
    if (! timeout){
        let timeout = setTimeout(() => {
            //On timeout, grap last message, send and clear data
            let msg = context.get('bufferMsg') || null;
            context.set('lastTime', Date.now());
            context.set('timeout', 0 );
            context.set('bufferMsg',null);
            if(msg){
                node.send(msg);
            }
        }, delay - (nowTime - lastTime));
        context.set('timeout',timeout);
    }

    //Buffer last message
    context.set('bufferMsg', msg);
}

1 Like

Yes, of course. That is the fallback if it cannot be done reasonably easily otherwise.

@Colin, a plain function node has been proposed.
But if you are willing to use a state machine (e.g. node-red-contrib-xstate-machine) things get easy.

[{"id":"0307ecee22b66ef9","type":"smxstate","z":"8d7193deb1042133","name":"","xstateDefinition":"// Available variables/objects/functions:\n// xstate\n// - .Machine\n// - .interpret\n// - .assign\n// - .send\n// - .sendParent\n// - .spawn\n// - .raise\n// - .actions\n//\n// Common\n// - setInterval, setTimeout, clearInterval, clearTimeout\n// - node.send, node.warn, node.log, node.error\n// - context.get, context.set\n// - flow.get, flow.set\n// - env.get\n// - util\n\nconst { assign } = xstate;\n\n// First define names guards, actions, ...\n\n/**\n * Guards\n */\nconst isCounterZero = (context, event) => {\n  return context.counter == 0;\n};\n\n/**\n * Actions\n */\nconst incrementCounter = assign({\n  counter: (context, event) => 1\n});\n\nconst zeroCounter = assign({\n  counter: (context, event) => 0\n});\n\n\nconst sendTrigger = assign({\n  counter: (context, event) => {\n    // Can send log messages via\n    //  - node.log\n    //  - node.warn\n    //  - node.error\n    //node.warn(\"RESET\");\n\n    // Can send messages to the second outport\n    // Specify an array to send multiple messages\n    // at once\n    //  - node.send(msg)\n    node.send({ topic: \"control\", payload: \"trigger\" });\n\n    return 0;\n  }\n});\n\n\n/***************************\n * Main machine definition * \n ***************************/\nreturn {\n  machine: {\n    predictableActionArguments: true,\n    context: {\n      counter: 0\n    },\n    initial: 'idle',\n    states: {\n      idle: {\n        on: {\n          incoming: { target: 'trigger' }\n        }\n      },\n      trigger: {\n        entry: ['sendTrigger', 'zeroCounter'],\n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': { target: 'wait' }\n        }\n      },\n      wait: {\n        on: {\n          incoming: { actions: 'incrementCounter' }\n        },\n        after: {\n          5000: [{ target: 'last', cond: 'isCounterZero' },\n          { target: 'trigger' }]\n        }\n      },\n      last: {\n        entry: ['sendTrigger', 'zeroCounter'],\n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': [{ target: 'idle', cond: 'isCounterZero' },\n          { target: 'wait' }]\n        }\n      }\n    }\n  },\n  // Configuration containing guards, actions, activities, ...\n  // see above\n  config: {\n    guards: { isCounterZero },\n    actions: { incrementCounter, sendTrigger, zeroCounter },\n    activities: {}\n  },\n  // Define listeners (can be an array of functions)\n  //    Functions get called on every state/context update\n  listeners: (data) => {\n    //node.warn(data.state + \":\" + data.context.counter);\n  }\n};","noerr":0,"x":740,"y":2980,"wires":[[],["df7cc341b1b286ef"]]},{"id":"477d97d9cbf08b23","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":2940,"wires":[["101d713325575ffc"]]},{"id":"68d3cf9c811f6ad4","type":"debug","z":"8d7193deb1042133","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":3080,"wires":[]},{"id":"df7cc341b1b286ef","type":"q-gate","z":"8d7193deb1042133","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":550,"y":3080,"wires":[["68d3cf9c811f6ad4"]]},{"id":"c85c02f985c5ea4e","type":"change","z":"8d7193deb1042133","name":"topic = incoming","rules":[{"t":"set","p":"topic","pt":"msg","to":"incoming","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":2980,"wires":[["0307ecee22b66ef9"]]},{"id":"7a1a70977035002f","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MSG A","payloadType":"str","x":250,"y":3080,"wires":[["89b26b6a10d4908f","65df77aa6ce0b183","3898a55a51e5d87f"]]},{"id":"89b26b6a10d4908f","type":"trigger","z":"8d7193deb1042133","name":"1s MSG B","op1":"","op2":"MSG B","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":250,"y":3120,"wires":[["65df77aa6ce0b183"]]},{"id":"3898a55a51e5d87f","type":"trigger","z":"8d7193deb1042133","name":"11s MSG C","op1":"","op2":"MSG C","op1type":"nul","op2type":"str","duration":"11","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":250,"y":3040,"wires":[["65df77aa6ce0b183"]]},{"id":"65df77aa6ce0b183","type":"junction","z":"8d7193deb1042133","x":380,"y":3040,"wires":[["101d713325575ffc"]]},{"id":"101d713325575ffc","type":"junction","z":"8d7193deb1042133","x":400,"y":2980,"wires":[["df7cc341b1b286ef","c85c02f985c5ea4e"]]}]

This is including the case found by @jbudd.

As author of the q-gate node, I want to help out here, but I have been too busy to follow the discussion in any depth. Based on the original post, I think this flow meets the requirements without the possibility of race conditions. (See this discussion and this one.)

[{"id":"67659f7786234008","type":"inject","z":"0ae9d1938fe1cb0d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":80,"wires":[["e8de0d8f5c30616b","92b84dfdf699239e"]]},{"id":"e8de0d8f5c30616b","type":"function","z":"0ae9d1938fe1cb0d","name":"queue after","func":"return [[msg,{payload:'queue',topic:'control'}]]","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":80,"wires":[["c453ff7e92e4428d"]]},{"id":"92b84dfdf699239e","type":"trigger","z":"0ae9d1938fe1cb0d","name":"open after 5s","op1":"","op2":"open","op1type":"nul","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":310,"y":120,"wires":[["1354ec7ffed7352c"]]},{"id":"c453ff7e92e4428d","type":"q-gate","z":"0ae9d1938fe1cb0d","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":650,"y":80,"wires":[["1248d518fe22581f"]]},{"id":"1354ec7ffed7352c","type":"change","z":"0ae9d1938fe1cb0d","name":"topic:control","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":120,"wires":[["c453ff7e92e4428d"]]},{"id":"1248d518fe22581f","type":"debug","z":"0ae9d1938fe1cb0d","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":80,"wires":[]}]

I could easily be mistaken, so if anyone can demonstrate a race condition here, either in theory or practice, please let me know.

Hi @drmibell, I think i does not meet the requirements.
Just clicking quickly on the trigger button I got msg quicker than 5s. Here is an example:

[{"id":"67659f7786234008","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":3940,"wires":[["86fe5414cef4edd6","9b9b63b53aa3f5cc","d46cb9e075df54a5"]]},{"id":"e8de0d8f5c30616b","type":"function","z":"8d7193deb1042133","name":"queue after","func":"return [[msg,{payload:'queue',topic:'control'}]]","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":3960,"wires":[["c453ff7e92e4428d"]]},{"id":"92b84dfdf699239e","type":"trigger","z":"8d7193deb1042133","name":"open after 5s","op1":"","op2":"open","op1type":"nul","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":630,"y":4000,"wires":[["1354ec7ffed7352c"]]},{"id":"c453ff7e92e4428d","type":"q-gate","z":"8d7193deb1042133","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":970,"y":3960,"wires":[["1248d518fe22581f"]]},{"id":"1354ec7ffed7352c","type":"change","z":"8d7193deb1042133","name":"topic:control","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":790,"y":4000,"wires":[["c453ff7e92e4428d"]]},{"id":"1248d518fe22581f","type":"debug","z":"8d7193deb1042133","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1210,"y":3960,"wires":[]},{"id":"9b9b63b53aa3f5cc","type":"trigger","z":"8d7193deb1042133","name":"4s","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"4","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":350,"y":3980,"wires":[["86fe5414cef4edd6"]]},{"id":"d46cb9e075df54a5","type":"trigger","z":"8d7193deb1042133","name":"5001 ms","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"5001","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":360,"y":4020,"wires":[["86fe5414cef4edd6"]]},{"id":"86fe5414cef4edd6","type":"junction","z":"8d7193deb1042133","x":520,"y":3960,"wires":[["e8de0d8f5c30616b","92b84dfdf699239e"]]}]

Quickly tested this scenario with the state machine and it passed.