Analog totalization with Node-RED

Using PAC Control it is fairly straightforward to do analog totalization – you just need a chart with three steps: initialize the totalizer, save it to a variable, and reset it as-needed. But what if you’re not running a strategy? Node-RED to the rescue again.

Note that if you’re using a groov RIO EMU or EPIC power monitoring module, totalization is built in with channels dedicated to measuring accumulated energy use, which can be reset using this flow from a previous forum thread: Resetting accumulated energy on EPIC PM and RIO EMU without PAC Control
However if you have a standard RIO or EPIC module for something like flow rate, you will need to manually accumulate values with either a strategy, flow, or custom shell script. In this post I’ll focus on the Node-RED flow and compare it to an equivalent PAC strategy.

To start, I’m basing my Node-RED logic off this chart:
PAC_totalizer

That lead to the creation of this flow:

Also, to confirm they are measuring the same thing, I plotted both in groov View, and you can see how closely they are aligned. I would still recommend using PAC when possible, but you can see that Node-RED does get a very accurate accumulated value.


So with that out of the way, which parts are the same, and which are different?
The most important thing is that the totalizer rate is what you want. I used 1s in my tests, but you could use hours (3600 seconds) or whatever else is appropriate for your application. In PAC Control this number is entered with the “Set Analog Totalizer Rate” command in either an action block or OptoScript. Once that has been initialized you will need to use the “Get Analog Totalizer Value” command to move the total into something you can read, like a float variable. From there you can move or read it wherever you need.
Then, that value will need to be reset, once again using a PAC Control command, this time “Get & Clear Analog Totalizer Value”. You can use the “Get” portion to save the previous value before it is reset, but either way this will clear the totalizer back to zero and start fresh at the end of a shift, end of the day, billing cycle, month, or whatever makes sense for your application. I am using the on-latch of a physical button to trigger the reset, so that also needs to be reset.

For Node-RED that totalizer rate is entered as the interval in the inject node. For this example I set it to inject once a second, that will then read the analog channel value and add it to a saved variable with context, flow, or global storage. If you’re not familiar with that, we have a video that goes over it in depth: How to use context storage in Node-RED to save data - YouTube.
This accumulated value is the same as the totalizer value calculated in Node-RED, as you can see by the groov View trend screenshot above. In this case I am writing that total to a groov View data store tag, but you could display / plot it with the Node-RED Dashboard, publish it via MQTT, save it to a database, email it out, or whatever else you want.
The reset for Node-RED is done by simply writing 0 to that saved variable and again clearing the reset, whether that’s a physical button with an on-latch, a UI button, or whatever you want to trigger you use to reset. That reset could also be done with a timed or scheduled inject as well, just make sure you save the variable value before resetting if you’ll need to refer back to it.


Here is the flow import JSON for you to try it out for yourself, but as always test out the flow for yourself to make sure it works as you expect before putting it into production. And as always, happy flowing!

[{"id":"22d75b85d3c5ddf8","type":"tab","label":"Analog Totalizer","disabled":false,"info":""},{"id":"4a1115b8e4f3041f","type":"inject","z":"22d75b85d3c5ddf8","name":"1s","props":[],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":100,"wires":[["3872ad338a98bcea"]]},{"id":"3872ad338a98bcea","type":"groov-io-read","z":"22d75b85d3c5ddf8","device":"9a7eb4187fec11ec","dataType":"channel-analog","moduleIndex":"3","channelIndex":"0","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","itemName":"","name":"","x":320,"y":100,"wires":[["4d3cda6cf4592f42"]]},{"id":"2b40aae8f0fcdf7b","type":"debug","z":"22d75b85d3c5ddf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":690,"y":60,"wires":[]},{"id":"c14ef759ea55c306","type":"groov-write-ds","z":"22d75b85d3c5ddf8","dataStore":"2da1eade3338db9b","tagName":"total","tableStartIndex":"","value":"","valueType":"msg.payload","name":"","x":670,"y":100,"wires":[[]]},{"id":"4d3cda6cf4592f42","type":"function","z":"22d75b85d3c5ddf8","name":"Totalizer","func":"var total = flow.get(\"total\") || 0;\ntotal += msg.payload;\nflow.set(\"total\", total);\nmsg.payload = total;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":100,"wires":[["2b40aae8f0fcdf7b","c14ef759ea55c306"]]},{"id":"250be5f4c59dd79c","type":"groov-io-input","z":"22d75b85d3c5ddf8","device":"9a7eb4187fec11ec","dataType":"channel-digital-on-latch","moduleIndex":"0","channelIndex":"1","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","sendInitialValue":true,"deadband":"1","scanTimeSec":"1.0","name":"reset","x":170,"y":160,"wires":[["a6daa14ae4f4f6af"]]},{"id":"527d11df82973d14","type":"change","z":"22d75b85d3c5ddf8","name":"","rules":[{"t":"set","p":"total","pt":"flow","to":"0","tot":"num"},{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":160,"wires":[["8ec55e23bd69ef16","c14ef759ea55c306","e7954e18a7b42cb6"]]},{"id":"8ec55e23bd69ef16","type":"debug","z":"22d75b85d3c5ddf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":690,"y":200,"wires":[]},{"id":"a6daa14ae4f4f6af","type":"switch","z":"22d75b85d3c5ddf8","name":"true?","property":"payload","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":310,"y":160,"wires":[["527d11df82973d14"]]},{"id":"e7954e18a7b42cb6","type":"groov-io-write","z":"22d75b85d3c5ddf8","device":"9a7eb4187fec11ec","dataType":"channel-clear-on-latch","moduleIndex":"0","channelIndex":"1","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"clear reset","x":690,"y":160,"wires":[[]]},{"id":"9a7eb4187fec11ec","type":"groov-io-device","address":"localhost","msgQueueFullBehavior":"DROP_OLD"},{"id":"2da1eade3338db9b","type":"groov-data-store","project":"da335844209776a0","dsName":"test"},{"id":"da335844209776a0","type":"groov-project","address":"localhost","msgQueueFullBehavior":"DROP_OLD"}]
3 Likes

The timing in Node-Red is not ultra-precise, so you need to be careful here. Don’t assume that you will get 60 injects per minute when inject is set for 1 second. In my experience you will get an interval slightly longer than 1 second.

If higher precision is required for the integration, the time between intervals should be calculated and used. I’ve used the node-red-contrib-interval-length node for this - which isn’t required, as you can store the time in the flow context and calculate the difference as well.

2 Likes

Great point philip, Node-RED gets close but it’s definitely not exact. I would recommend always using PAC Control where possible and just use Node-RED as a workaround / fallback option.

Measuring timestamps would be a great way to get more accuracy, especially over longer time periods like days / weeks.