How to manually code report-by-exception (RBE) with JavaScript

When it comes to controlling a flow in Node-RED, one of the most useful core nodes is the rbe or “report-by-exception” node. This node can be configured to block a message from moving through the flow unless it’s a new value, or unless it changes by a certain amount.

This is a great node to use along-side other flow control nodes like the switch, change, and function nodes to eliminate redundant messages or reduce the amount of messages by putting in a deadband. While this is really useful sometimes it can make flows look a little messier since you have to have an entire extra node in there just for this functionality, so in this post I’ll share my method of getting “rbe” functionality with standard JavaScript, so you can eliminate one of the nodes in your flow or just have more exact control over the process.

Please note that this is just intended to be an example and you should always review and modify code to meet your needs before deploying it. I can’t guarantee it’ll do what you want, I just want to share my approach to this problem.


image

This first example replicates the rbe mode “block unless value changes”, which is a pretty typical use case. You can also see that the initial value does go through the flow. Right after the last_output is first saved to context in line 7, you can instead choose to return null; and that first message will be blocked and just used to compare against the next incoming value.
You could also write other code rather than returning right away, this is just the basic functionality of the rbe node for the sake of example.
I’ve tested this flow with true/false booleans, numbers, and strings and they all work as-expected.

[{"id":"8d7022a3.696ba","type":"function","z":"ff89f143.8a546","name":"rbe","func":"// block unless value changes\n\nvar new_input = msg.payload;\nvar last_output = context.get(\"last_output\");\n\nif (typeof last_output == 'undefined') {\n    context.set(\"last_output\", new_input);\n    return msg; // forward initial value\n    // * return null to ignore the initial value * //\n}\n\n// otherwise last_output is defined, so did it change?\nif (new_input == last_output) {\n    return null;\n}\nelse {\n    context.set(\"last_output\", new_input);\n    return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":370,"y":1320,"wires":[["d4a72447.8880f8"]]},{"id":"d4a72447.8880f8","type":"debug","z":"ff89f143.8a546","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":530,"y":1320,"wires":[]},{"id":"99767981.a930f8","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":210,"y":1280,"wires":[["8d7022a3.696ba"]]},{"id":"560e81af.07a11","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"22","payloadType":"num","x":210,"y":1320,"wires":[["8d7022a3.696ba"]]},{"id":"11078d1f.06ca13","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-13","payloadType":"num","x":210,"y":1360,"wires":[["8d7022a3.696ba"]]}]

This next example uses a deadband value that I’ve hard-coded into the node, although another advantage of using JavaScript in a function node instead of the rbe node is that you could pull this value from anywhere you want, set it dynamically with a message property, even change it on-the-fly while the flow is running.
One thing to note is that under else, on line 28, I have commented out an additional context.set() line that can be used to save “last_output” so that the last input is used is used for comparison, rather than the last output. This essentially means that a value can slowly increase and as long as it doesn’t increase by 5 or more within the space of one reading, and it will always be blocked.
It’s weird to explain, but it makes a lot more sense after some experimenting. Feel free to import the flow and give it a try:

[{"id":"47baf088.4d738","type":"function","z":"ff89f143.8a546","name":"deadband","func":"// block unless value change is greater or equal to\n\nvar deadband = 5;\n\nvar new_input = parseFloat(msg.payload);\nvar last_output = context.get(\"last_output\");\n\nif (typeof last_output == 'undefined') {\n    context.set(\"last_output\", new_input);\n    return msg; // forward initial value\n    // * return null to ignore the initial value * //\n}\n\ndeadband_low = last_output - deadband;\ndeadband_high = last_output + deadband;\n\n// if it's beyond the deadband limits, it \"passes\" the test\nif(new_input <= deadband_low || new_input >= deadband_high) {\n    context.set(\"last_output\", new_input);  // update the last valid output value\n    return msg;                             // forward the message since it's outside the deadband\n}\n\n// otherwise do not return message, end here (return null)\nelse {\n    /* Uncomment the line below to compare to the last *input* value\n        Otherwise it will only compare to the last valid *output* value  */\n        \n    // context.set(\"last_output\", new_input); // to compare to last input value\n    return null;\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":1480,"wires":[["65d6822a.c6586c"]]},{"id":"65d6822a.c6586c","type":"debug","z":"ff89f143.8a546","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":1480,"wires":[]},{"id":"d6b9e4f.653cc18","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"5","payloadType":"num","x":410,"y":1640,"wires":[["47baf088.4d738"]]},{"id":"e5cecc63.0f78f","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"6","payloadType":"num","x":410,"y":1680,"wires":[["47baf088.4d738"]]},{"id":"b9d59c7.6561a6","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":410,"y":1560,"wires":[["47baf088.4d738"]]},{"id":"f48f7635.c499f8","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-4","payloadType":"num","x":410,"y":1480,"wires":[["47baf088.4d738"]]},{"id":"da73444a.e7b4c8","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-3","payloadType":"num","x":410,"y":1520,"wires":[["47baf088.4d738"]]},{"id":"549680f5.80b5","type":"inject","z":"ff89f143.8a546","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":410,"y":1600,"wires":[["47baf088.4d738"]]}]

If you have any questions or suggestions about either of these, or decide to modify / use them for your own application please drop a comment in the thread below!

And as always, happy coding.

1 Like

Not to take anything away from Terry’s code, but he is just responding / quoting the reason the customer gave as to why he wanted to add the RBE code to a function block - to visually remove it from the flow.
If you can code like Terry, then you are well on your way, but for the rest of us mortals, here is a tip that you may not know about to clean up the look of your flow perhaps just a little…

image

Here is a Function and RBE node in a bit of my messy flow.
You can see how you can shrink them down a lot.
Here is how;

Double click on the node, then click on Appearance.

Take the check out of the Show such that it says Hide.

Sure, its not for everyone (you lose the label for starters) and it only cleans things up a bit, but I have found it really helpful, epecially for those little single function RBE type nodes.

1 Like