Combination of Rate Limit and RBE node?

I have a message coming in every 5 seconds containing 0 or 1 (it is the required state of a relay output). I want to log it to Influx but I only want to store it every five minutes unless it changes. It seems I need a sort of combined Rate Limit node and RBE where it rate limits but lets through immediately when the value changes. I haven't managed to come up with a neat solution to this, so this afternoons competition is to see who can come up with the most pleasing suggestion.
I don't want to use an RBE followed by a message repeater as, if the input messages stop, then I want to stop logging to the database.
I could do it quite easily in a function node, but that is cheating.

[{"id":"e29e4e5b.2918e","type":"inject","z":"eeee575.b4b80a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":160,"y":1000,"wires":[["b78580a3.76d19"]]},{"id":"b78580a3.76d19","type":"rbe","z":"eeee575.b4b80a8","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":310,"y":1000,"wires":[["7d113250.989adc","15bdbeaa.83fe21"]]},{"id":"7d113250.989adc","type":"trigger","z":"eeee575.b4b80a8","name":"","op1":"","op2":"","op1type":"pay","op2type":"pay","duration":"-2","extend":false,"overrideDelay":false,"units":"s","reset":"reset","bytopic":"all","topic":"topic","outputs":1,"x":520,"y":1000,"wires":[["39a0220.c3dc8de"]]},{"id":"93163cd.0580bc","type":"inject","z":"eeee575.b4b80a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":1040,"wires":[["b78580a3.76d19"]]},{"id":"15bdbeaa.83fe21","type":"trigger","z":"eeee575.b4b80a8","name":"","op1":"","op2":"reset","op1type":"nul","op2type":"str","duration":"10","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":410,"y":1080,"wires":[["7d113250.989adc"]]},{"id":"39a0220.c3dc8de","type":"debug","z":"eeee575.b4b80a8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":710,"y":1000,"wires":[]}]

If I keep inputting the same value then after a bit that flow stops until I send a change. All the time I keep inputting the same value it should output it regularly but at a slower rate. Then interject extra ones when the input changes.

that is what that flow does... once you send something it sends every two seconds (change to 5 mins in your case) - but should send changes (let through by rbe)... but stops after a while (set reset trigger to whenever you want to stop it logging)

Here is my effort needs context store though.

[{"id":"11b606e8.7ca019","type":"inject","z":"8d22ae29.7df6d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"str","x":90,"y":380,"wires":[["2f87e709.5ed798","f5131913.a601a8"]]},{"id":"b6b0490a.f74158","type":"inject","z":"8d22ae29.7df6d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"str","x":90,"y":440,"wires":[["2f87e709.5ed798","f5131913.a601a8"]]},{"id":"f5131913.a601a8","type":"delay","z":"8d22ae29.7df6d","name":"","pauseType":"delay","timeout":"200","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":220,"y":440,"wires":[["ab2045ad.f67ec"]]},{"id":"2f87e709.5ed798","type":"rbe","z":"8d22ae29.7df6d","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":310,"y":380,"wires":[["c7d100e2.fea9","73b7e636.b031c8"]]},{"id":"ab2045ad.f67ec","type":"switch","z":"8d22ae29.7df6d","name":"","property":"time","propertyType":"flow","rules":[{"t":"lt","v":"$toMillis($now()) ","vt":"jsonata"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":380,"y":420,"wires":[["179f8199.4651a6"],[]]},{"id":"c7d100e2.fea9","type":"change","z":"8d22ae29.7df6d","name":"","rules":[{"t":"set","p":"time","pt":"flow","to":"($toMillis($now())  + 5000)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":380,"wires":[[]]},{"id":"179f8199.4651a6","type":"change","z":"8d22ae29.7df6d","name":"","rules":[{"t":"set","p":"time","pt":"flow","to":"($toMillis($now())  + 5000)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":420,"wires":[["73b7e636.b031c8"]]},{"id":"73b7e636.b031c8","type":"debug","z":"8d22ae29.7df6d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":670,"y":380,"wires":[]}]

All the time I keep clicking 0 for example it should keep sending out (rate limited) zeros.

Yes, that does work, I am sure you would agree that it isn't very pretty though.

Don't know about pretty, It seems to be more robust. I tried @dceejay way, but the ugly route worked better for me.

How about this one

The second rbe is to suppress duplicate messages when the same one is emitted by both the rate limit and the rbe nodes.

[{"id":"a8ab2ec3.838e38","type":"delay","z":"bdd7be38.d3b55","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":280,"y":4620,"wires":[["baaa13ea.e41a6"]]},{"id":"fc72e97d.d02f78","type":"rbe","z":"bdd7be38.d3b55","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":280,"y":4660,"wires":[["baaa13ea.e41a6"]]},{"id":"fa1143fa.826cb","type":"inject","z":"bdd7be38.d3b55","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":90,"y":4620,"wires":[["a8ab2ec3.838e38","fc72e97d.d02f78"]]},{"id":"8f6dc68d.e4811","type":"inject","z":"bdd7be38.d3b55","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":90,"y":4660,"wires":[["a8ab2ec3.838e38","fc72e97d.d02f78"]]},{"id":"baaa13ea.e41a6","type":"rbe","z":"bdd7be38.d3b55","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"_msgid","x":450,"y":4620,"wires":[["bc41c698.c69a7"]]},{"id":"bc41c698.c69a7","type":"debug","z":"bdd7be38.d3b55","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":550,"y":4680,"wires":[]}]

bad boy corrupt flow.

Aargh! Hoist with mine own petard.

Fixed now.

1 Like

Yes i tried that earlier, never thought of using _msgid though.

Edit/ Yes pretty and robust.

Not sure that is actually a good idea though, it is possible for multiple messages to have the same id, it depends how they originated. To make it safe I think I would have to put a change node on the front putting a reasonably unique id in the message. Can I do that reasonably efficiently with jsonata in a change node?

just add a timestamp to the message.

Good thinking. So this seems to be the best so far

[{"id":"a8ab2ec3.838e38","type":"delay","z":"bdd7be38.d3b55","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":470,"y":4620,"wires":[["baaa13ea.e41a6"]]},{"id":"fc72e97d.d02f78","type":"rbe","z":"bdd7be38.d3b55","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":450,"y":4660,"wires":[["baaa13ea.e41a6"]]},{"id":"fa1143fa.826cb","type":"inject","z":"bdd7be38.d3b55","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":90,"y":4620,"wires":[["7ba6c786.a8ab3"]]},{"id":"8f6dc68d.e4811","type":"inject","z":"bdd7be38.d3b55","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":90,"y":4660,"wires":[["7ba6c786.a8ab3"]]},{"id":"baaa13ea.e41a6","type":"rbe","z":"bdd7be38.d3b55","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"timestamp","x":630,"y":4620,"wires":[["bc41c698.c69a7"]]},{"id":"bc41c698.c69a7","type":"debug","z":"bdd7be38.d3b55","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":4680,"wires":[]},{"id":"7ba6c786.a8ab3","type":"change","z":"bdd7be38.d3b55","name":"Set msg.timestamp","rules":[{"t":"set","p":"timestamp","pt":"msg","to":"$millis()\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":4640,"wires":[["a8ab2ec3.838e38","fc72e97d.d02f78"]]}]

Though, having also tried writing it in a Function node, it turns out to be very simple, so perhaps it is better just to use that.

const minInterval = 5000       // min interval between outputs, msec
const previous = context.get("previous") || {timestamp: 0}
const now = new Date().getTime()
if ( msg.payload == previous.value  && (now - previous.timestamp) < minInterval )
{
    // same value as last one and too soon to send again, so send nothing
    msg = null
} else {
    previous.timestamp = now
    previous.value = msg.payload
    context.set("previous", previous)
}
return msg;

Colin, the way I look at it is, if the visual/node version does nothing to aid comprehension & uses many more nodes, then there is nowt wrong with a function node. Just litter it with comments and give it a good name.

1 Like

Yes, I tend to agree.