Variable Rate Limit using Delay Node?

Using the normal Delay node, you can use msg.delay to overwrite the preset value, and dynamically change it based on what you're doing.

Is there an option to do something similar with the Rate Limit side of the Delay node?

I am trying to set up a camera motion detection notification system, in which you would be able to dynamically change how often you get messages. So for example, I would have an input_number slider in home assistant, from 0-60. And I would like to use that number to set a limit to 1 message per 0-60 mins.

If there is not, what would be the best way to approach this issue? The Rate Limit part of the Delay node seems perfect for the job, unless you can't use it dynamically like you can with the Delay part of it.

While waiting for an official answer, I had a play to see if the end result could be achieved with existing nodes and came up with this POC

[{"id":"6c310772.a6ba78","type":"debug","z":"d6547d57.a4174","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1090,"y":140,"wires":[]},{"id":"d90146a0.c79ef8","type":"inject","z":"d6547d57.a4174","name":"","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":260,"wires":[["ee70978.9f3d068"]]},{"id":"ee70978.9f3d068","type":"change","z":"d6547d57.a4174","name":"set flow.rate","rules":[{"t":"move","p":"payload","pt":"msg","to":"rate","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":250,"y":260,"wires":[[]]},{"id":"4fd10a4f.a90bb4","type":"inject","z":"d6547d57.a4174","name":"","topic":"","payload":"2","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":320,"wires":[["ee70978.9f3d068"]]},{"id":"c2840aa6.696cf8","type":"inject","z":"d6547d57.a4174","name":"","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":380,"wires":[["ee70978.9f3d068"]]},{"id":"c94255d5.bb4758","type":"switch","z":"d6547d57.a4174","name":"Switch depending on flow.rate","property":"rate","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"lte","v":"10","vt":"num"},{"t":"lte","v":"30","vt":"num"}],"checkall":"false","repair":false,"outputs":4,"x":390,"y":140,"wires":[["f4497ebb.8086"],["19b867a.d7a4698"],["e9b50038.e54f2"],["2f9d9737.0399a8"]]},{"id":"f4497ebb.8086","type":"delay","z":"d6547d57.a4174","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":670,"y":60,"wires":[["f5ed6c54.182db"]]},{"id":"19b867a.d7a4698","type":"delay","z":"d6547d57.a4174","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"30","nbRateUnits":"1","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":680,"y":120,"wires":[["9311b66b.fb56b8"]]},{"id":"e9b50038.e54f2","type":"delay","z":"d6547d57.a4174","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"6","nbRateUnits":"1","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":670,"y":180,"wires":[["ca3024ca.a615c8"]]},{"id":"9311b66b.fb56b8","type":"change","z":"d6547d57.a4174","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":120,"wires":[["6c310772.a6ba78"]]},{"id":"f5ed6c54.182db","type":"change","z":"d6547d57.a4174","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":60,"wires":[["6c310772.a6ba78"]]},{"id":"ca3024ca.a615c8","type":"change","z":"d6547d57.a4174","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":180,"wires":[["6c310772.a6ba78"]]},{"id":"54706bbd.d07114","type":"inject","z":"d6547d57.a4174","name":"","topic":"","payload":"","payloadType":"date","repeat":"0.5","crontab":"","once":true,"onceDelay":0.1,"x":130,"y":140,"wires":[["c94255d5.bb4758"]]},{"id":"593bc0b1.7e4eb","type":"inject","z":"d6547d57.a4174","name":"","topic":"","payload":"30","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":440,"wires":[["ee70978.9f3d068"]]},{"id":"2f9d9737.0399a8","type":"delay","z":"d6547d57.a4174","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"2","nbRateUnits":"1","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":670,"y":240,"wires":[["3b0b036e.ec1dbc"]]},{"id":"3b0b036e.ec1dbc","type":"change","z":"d6547d57.a4174","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"30","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":240,"wires":[["6c310772.a6ba78"]]}]

Have a play with it (use your slider as input to the set flow.rate change node) and see if it functionally does what your after

1 Like

The simple answer is that it isn't currently variable.
However - I think there are several things that need thinking about before rushing to "just add it".

Currently the way it works is that on first arrival an interval timeout is set and anything that then arrives is then either queued or discarded - on the next tick the next item to arrive or top of the queue is released.

What should the behaviour be when changing the rate ? If extending the rate - does the current timer expire (which may mean still waiting for the rest of the hour - for example) ? then have to be reset with the new value ? or be cancelled and the new one set for some partial interval - before finally setting the real interval ? If the timer is cancelled - does the next value get sent immediately (it would do by default) - which could mean if you changed it a lot (eg via a slider) that a lot of values may suddenly come through (one per reset) - violating the rate wanted.

All needs some thought.

Thanks guys for both of your inputs!

I ended up creating something a bit similar to your creation @cymplecy before I returned to look at this post.

It's kind of silly, but this is what I ended up with:

I limited the input_number slider to only increase/decrease at steps of 5, and then used a giant switch statement to push it through to the right rate limiter. I shoved it all into a sub-flow that I can re-use, for each different family member or device on the receiving end of these notifications, per camera (Front yard, back yard, etc). There are probably ways it could be done better, but hey, it works and does what I need it to.

@dceejay You do raise a lot of good points and questions. The intended use would be more so of a "set and forget", so the slider wouldn't be moved around too much once each person got their settings set just right to their preference. But yes, if the slider was moved at the current moment it would reset the limiter. But I'm okay with that for the time being, because like I said it shouldn't be moved around terribly often. Gotta make compromises every now and then.

Thanks again everyone for your ideas and your input.

Just a little note - if you feed a slider into a trigger node - you can get it to ignore any short term slider changes and only output the last position

Also just to mention, you can configure the slider to only send when you release
"only on release"

1 Like

Note these are demos to show functionality not the actual way to code :slight_smile:

I've come up with a variable rate flow emulator using a function node in conjunction with the delay node set to variable delay dependant on msg.delay

This is so people can try it out and examine/discuss variable rate limiting behaviour
Version1

Code for the function node (copy and pasted from JS generated by Blocky)

[{"id":"a29de14a.f8621","type":"function","z":"71db5e1b.7cbed","name":"Rate Limit","func":"var rate, lastTime;\n\n\nrate = (msg['rate']);\nif (context.get('lastTime')) {\n  lastTime = (context.get('lastTime'));\n} else {\n  lastTime = new Date().getTime() - 1000000;\n}\nif (rate <= new Date().getTime() - lastTime) {\n  context.set('lastTime', new Date().getTime());\n  msg['delay'] = 0;\n} else {\n  context.set('lastTime', (rate + lastTime));\n  msg['delay'] = (rate - (new Date().getTime() - lastTime));\n}\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":640,"wires":[["e888583a.0563b8"]]},{"id":"48c9ceba.5a6b1","type":"inject","z":"71db5e1b.7cbed","name":"","topic":"","payload":"3000","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":640,"wires":[["28fb0f44.245bb"]]},{"id":"28fb0f44.245bb","type":"change","z":"71db5e1b.7cbed","name":"","rules":[{"t":"set","p":"rate","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":640,"wires":[["a29de14a.f8621"]]},{"id":"dc85db79.91cf48","type":"inject","z":"71db5e1b.7cbed","name":"","topic":"","payload":"6000","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":680,"wires":[["28fb0f44.245bb"]]},{"id":"e888583a.0563b8","type":"delay","z":"71db5e1b.7cbed","name":"","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":820,"y":640,"wires":[["c9b891fb.a0587"]]},{"id":"c9b891fb.a0587","type":"debug","z":"71db5e1b.7cbed","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1010,"y":640,"wires":[]}]

Best played with rather than reading description but here it is anyway :slight_smile:

1st message gets passed straight thru with no delay

If a message arrives after the last one has passed thru the delay node - it gets sent on with no delay as well.

If a message arrives before previous rate limit delay period has passed, then the message is delayed the appropriate amount of time so it matches.

This approach means that the rate flow of any messages in the queue is honoured when they enter the queue and not changed in retrospect

Version 2 (needs node-red-contrib-queue-gate)
This shows a demo that sends 8 messages thru it and that lets you vary the flow rate so that the flow rate is applied to the next message released from the q-gate buffer which means better dynamic control of the rate

[{"id":"f7ad7e0c.1ce4e","type":"debug","z":"d0817fc5.e088c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1150,"y":400,"wires":[]},{"id":"96a20a4c.c88258","type":"inject","z":"d0817fc5.e088c","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":true,"onceDelay":0.1,"x":170,"y":440,"wires":[["e891be0.76c214"]]},{"id":"e891be0.76c214","type":"q-gate","z":"d0817fc5.e088c","name":"Source Gate","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","maxQueueLength":"8","keepNewest":false,"qToggle":false,"persist":false,"x":350,"y":440,"wires":[["f0502eaf.fe599"]]},{"id":"8bcca828.441e98","type":"inject","z":"d0817fc5.e088c","name":"","topic":"control","payload":"flush","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":480,"wires":[["e891be0.76c214"]]},{"id":"acc5aa53.53f0a8","type":"inject","z":"d0817fc5.e088c","name":"","topic":"","payload":"3000","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":520,"wires":[["658cf04.5ff061"]]},{"id":"45f57cc6.b2e4b4","type":"inject","z":"d0817fc5.e088c","name":"","topic":"","payload":"6000","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":560,"wires":[["658cf04.5ff061"]]},{"id":"cae57c47.e0d73","type":"change","z":"d0817fc5.e088c","name":"","rules":[{"t":"set","p":"rate","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":520,"wires":[[]]},{"id":"f0502eaf.fe599","type":"delay","z":"d0817fc5.e088c","name":"Needed for demo","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"100","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":440,"wires":[["42359b0c.6d46a4"]]},{"id":"4fcfdd33.0c3fa4","type":"q-gate","z":"d0817fc5.e088c","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","maxQueueLength":"0","keepNewest":false,"qToggle":false,"persist":false,"x":930,"y":460,"wires":[["af5422d5.ddd82"]]},{"id":"af5422d5.ddd82","type":"function","z":"d0817fc5.e088c","name":"","func":"\nreturn msg;","outputs":1,"noerr":0,"x":995,"y":400,"wires":[["f7ad7e0c.1ce4e","e5e46da4.85801"]],"icon":"node-red/arrow-in.svg","l":false},{"id":"448679f1.7d5a98","type":"inject","z":"d0817fc5.e088c","name":"","topic":"control","payload":"trigger","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":520,"wires":[["e891be0.76c214"]]},{"id":"42359b0c.6d46a4","type":"switch","z":"d0817fc5.e088c","name":"","property":"timerRunning","propertyType":"flow","rules":[{"t":"false"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":810,"y":440,"wires":[["af5422d5.ddd82"],["4fcfdd33.0c3fa4"]]},{"id":"e7fbd260.b6274","type":"inject","z":"d0817fc5.e088c","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":895,"y":580,"wires":[["9390f30d.f5ce7"]],"l":false},{"id":"9390f30d.f5ce7","type":"change","z":"d0817fc5.e088c","name":"","rules":[{"t":"set","p":"timerRunning","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"reset","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":935,"y":580,"wires":[["4fcfdd33.0c3fa4"]],"l":false},{"id":"658cf04.5ff061","type":"ui_slider","z":"d0817fc5.e088c","name":"","label":"Rate Slider","tooltip":"","group":"7301eab1.cc8794","order":0,"width":0,"height":0,"passthru":true,"outs":"all","topic":"","min":"1000","max":"10000","step":"1000","x":510,"y":520,"wires":[["cae57c47.e0d73","199b0f79.75b8e1"]]},{"id":"199b0f79.75b8e1","type":"ui_text","z":"d0817fc5.e088c","group":"7301eab1.cc8794","order":1,"width":0,"height":0,"name":"","label":"Milliseconds","format":"{{msg.payload}}","layout":"row-left","x":690,"y":560,"wires":[]},{"id":"e5e46da4.85801","type":"change","z":"d0817fc5.e088c","name":"","rules":[{"t":"set","p":"timerRunning","pt":"flow","to":"true","tot":"bool"},{"t":"set","p":"delay","pt":"msg","to":"rate","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":1055,"y":460,"wires":[["1c241624.23bf6a"]],"l":false},{"id":"1c241624.23bf6a","type":"delay","z":"d0817fc5.e088c","name":"","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1140,"y":460,"wires":[["e1db9e30.5557"]]},{"id":"e1db9e30.5557","type":"change","z":"d0817fc5.e088c","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"trigger","tot":"str"},{"t":"set","p":"timerRunning","pt":"flow","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":1235,"y":460,"wires":[["4fcfdd33.0c3fa4"]],"l":false},{"id":"3cdd8356.c5224c","type":"comment","z":"d0817fc5.e088c","name":"Test Message Stream - press control:flush to start","info":"","x":240,"y":400,"wires":[]},{"id":"782f4a2b.c16174","type":"inject","z":"d0817fc5.e088c","name":"","topic":"","payload":"9000","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":350,"y":600,"wires":[["658cf04.5ff061"]]},{"id":"7301eab1.cc8794","type":"ui_group","z":"","name":"Rate Group","tab":"f13a38e4.830558","disp":true,"width":"6","collapse":false},{"id":"f13a38e4.830558","type":"ui_tab","z":"","name":"Rate Tab","icon":"dashboard","disabled":false,"hidden":false}]
1 Like