Flow to set PAC Control variables using CSV recipes

If you’ve ever needed to quickly change an entire strategy’s worth of PAC Control tags back and forth between specific pre-set values, this post is for you!

This flow takes tag “recipes” as comma-separated value (CSV) files, breaks the files down, and then uses that data to set any number of PAC Control tags that you need – all triggered by input from groov View:


With this flow you can make as many recipe files as you need, and simply set the file name (#1-6 in this case) using groov View to immediately have those values applied to your PAC Control tags.
These files take the format “type,tagName,value” on each new line. The type must be int32, int64, float, or string exactly (no capital letters or spaces), followed by the PAC Control tag name, and finally the value the tag should be set to.
Here’s a complete recipe-#.csv example:

int32,count,122
float,pi,3.14159
string,MyString,testing
int32,pause,500
int64,Opto,22

Which applies to the values in PAC Control right away:
image

In order for this to work with groov View you can simply have a groov View tag set by a series of command buttons that will update the value based on the recipe you select:
image image

Finally you will need to load each of the recipe-#.csv files into the unsecured file area of your groov EPIC so that Node-RED can access them. You can either create these files directly in Node-RED or just drop them in the file area via groov Manage.

Here is the example CSV file to get you started: recipe-5.zip (237 Bytes),
and here is the flow import code for you to try it yourself:


[{"id":"5bfc7774.a81ca8","type":"switch","z":"72ff86de.983b78","name":"type?","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"int32","vt":"str"},{"t":"eq","v":"int64","vt":"str"},{"t":"eq","v":"float","vt":"str"},{"t":"eq","v":"string","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":5,"x":690,"y":1340,"wires":[["fec057cc.6174b8"],["76c7bc62.4dbc44"],["a9e16c44.23a67"],["4821b52e.c5f3ac"],["2d9e3199.8f91ce"]]},{"id":"30e8b89e.6374f8","type":"function","z":"72ff86de.983b78","name":"type,tag,value","func":"var write = {}; // write object to be sent out\ndata = msg.payload; // make a copy of msg.payload\ntype = data.indexOf(','); // find the commas in \"type,tag,value\"\ntag = data.indexOf(',', type+1); // offset by 1 to skip the first comma\n\nwrite.type = data.substring(0,type); // first field is the data type\nwrite.tagName = data.substring(type+1,tag); // second field is the tag name\nwrite.value = data.substring(tag+1,data.length); // final field is the write value\n\nreturn { payload : write }; // now we have payload.tagName and payload.value for the PAC node","outputs":1,"noerr":0,"x":500,"y":1360,"wires":[["5bfc7774.a81ca8"]]},{"id":"fec057cc.6174b8","type":"pac-write","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"int32-variable","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"int32","x":890,"y":1260,"wires":[[]]},{"id":"76c7bc62.4dbc44","type":"pac-write","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"int64-variable","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"int64","x":890,"y":1300,"wires":[[]]},{"id":"a9e16c44.23a67","type":"pac-write","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"float-variable","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"float","x":890,"y":1340,"wires":[[]]},{"id":"4821b52e.c5f3ac","type":"pac-write","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"string-variable","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"string","x":890,"y":1380,"wires":[[]]},{"id":"2d9e3199.8f91ce","type":"debug","z":"72ff86de.983b78","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":890,"y":1420,"wires":[]},{"id":"af341aeb.561788","type":"file in","z":"72ff86de.983b78","name":"recipe-#.csv","filename":"","format":"lines","chunk":false,"sendError":false,"encoding":"none","x":330,"y":1360,"wires":[["30e8b89e.6374f8"]]},{"id":"5da1a2b6.3b040c","type":"change","z":"72ff86de.983b78","name":"set filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"\"/home/dev/unsecured/recipe-\" & msg.payload & \".csv\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":170,"y":1360,"wires":[["af341aeb.561788"]]},{"id":"d2473d07.00bdb","type":"rbe","z":"72ff86de.983b78","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":460,"y":1300,"wires":[["5da1a2b6.3b040c"]]},{"id":"e9b45fcf.fc045","type":"groov-read-ds","z":"72ff86de.983b78","dataStore":"18a28a95.517b05","tagName":"recipe","tableStartIndex":"","tableLength":"","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"recipe #","x":320,"y":1300,"wires":[["d2473d07.00bdb"]]},{"id":"d116b38b.8801a","type":"inject","z":"72ff86de.983b78","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":true,"onceDelay":0.1,"x":170,"y":1300,"wires":[["e9b45fcf.fc045"]]},{"id":"1b17b31a.79d5ad","type":"pac-device","z":0,"address":"localhost","protocol":"https"},{"id":"18a28a95.517b05","type":"groov-data-store","z":0,"project":"6f94d393.9ebf4c","dsName":"NodeRED"},{"id":"6f94d393.9ebf4c","type":"groov-project","z":0,"address":"localhost"}]


Please comment below if you have any questions or specific use-cases for this code, and feel free to modify and reuse it however you need.

Happy coding!

2 Likes

Bonus round! Here’s a flow that can programatically create the .CSV recipe files for you:


This flow will read every single int32, int64, float, and string tag name and their current values from your PAC Control strategy, and then write each tag out as a separate line following the “type,tagName,value” format from the post above.

Note that this does put every single tag into the recipe file, so if there’s any values you don’t want changed by the recipe just delete those lines from the CSV file and they will not be rewritten.

To use this flow just put your recipe file name into the “set filename” change node, and hit the inject node. In this example I am creating recipe-6.csv, but you can change it to be whatever you like. When you inject the timestamp you will populate this file with each of the PAC Control tag lines. Also be aware that this just adds lines to the end of the file, so if you run the flow twice you will get duplicates!

Here’s the flow import code:


[{"id":"bf55edd9.b2086","type":"change","z":"72ff86de.983b78","name":"int64,tag,value","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"int64,\" & msg.payload.name & \",\" & msg.payload.value","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1560,"wires":[["272485de.44fdaa"]]},{"id":"76c71dfb.509e64","type":"split","z":"72ff86de.983b78","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":550,"y":1560,"wires":[["bf55edd9.b2086"]]},{"id":"272485de.44fdaa","type":"file","z":"72ff86de.983b78","name":"write line","filename":"","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":860,"y":1560,"wires":[[]]},{"id":"50a04a9a.9b1954","type":"pac-read","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"int64-variable","tagName":"","tableStartIndex":"","tableLength":"","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":390,"y":1560,"wires":[["76c71dfb.509e64"]]},{"id":"f0c0484d.c74528","type":"change","z":"72ff86de.983b78","name":"set filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"/home/dev/unsecured/recipe-6.csv","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":170,"y":1580,"wires":[["926799a.3119968","50a04a9a.9b1954","d1b784cd.00bc18","6003e9b4.dc1f88"]]},{"id":"699ee656.cec548","type":"inject","z":"72ff86de.983b78","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":1520,"wires":[["f0c0484d.c74528"]]},{"id":"926799a.3119968","type":"pac-read","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"int32-variable","tagName":"","tableStartIndex":"","tableLength":"","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":390,"y":1520,"wires":[["b8d04387.f0913"]]},{"id":"d1b784cd.00bc18","type":"pac-read","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"float-variable","tagName":"","tableStartIndex":"","tableLength":"","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":390,"y":1600,"wires":[["6d6e47ea.d15968"]]},{"id":"6003e9b4.dc1f88","type":"pac-read","z":"72ff86de.983b78","device":"1b17b31a.79d5ad","dataType":"string-variable","tagName":"","tableStartIndex":"","tableLength":"","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":400,"y":1640,"wires":[["9ddd0e89.ae8b6"]]},{"id":"b8d04387.f0913","type":"split","z":"72ff86de.983b78","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":550,"y":1520,"wires":[["e1d40f7a.bf308"]]},{"id":"6d6e47ea.d15968","type":"split","z":"72ff86de.983b78","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":550,"y":1600,"wires":[["fc949f6d.c05c2"]]},{"id":"9ddd0e89.ae8b6","type":"split","z":"72ff86de.983b78","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":550,"y":1640,"wires":[["de7a20cc.b2fc"]]},{"id":"e1d40f7a.bf308","type":"change","z":"72ff86de.983b78","name":"int32,tag,value","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"int32,\" & msg.payload.name & \",\" & msg.payload.value","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1520,"wires":[["9e6f0943.298138"]]},{"id":"fc949f6d.c05c2","type":"change","z":"72ff86de.983b78","name":"float,tag,value","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"float,\" & msg.payload.name & \",\" & msg.payload.value","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1600,"wires":[["25efc969.04c286"]]},{"id":"de7a20cc.b2fc","type":"change","z":"72ff86de.983b78","name":"string,tag,value","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"string,\" & msg.payload.name & \",\" & msg.payload.value","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1640,"wires":[["32fc2ce3.834a54"]]},{"id":"9e6f0943.298138","type":"file","z":"72ff86de.983b78","name":"write line","filename":"","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":860,"y":1520,"wires":[[]]},{"id":"25efc969.04c286","type":"file","z":"72ff86de.983b78","name":"write line","filename":"","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":860,"y":1600,"wires":[[]]},{"id":"32fc2ce3.834a54","type":"file","z":"72ff86de.983b78","name":"write line","filename":"","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":860,"y":1640,"wires":[[]]},{"id":"1b17b31a.79d5ad","type":"pac-device","z":0,"address":"localhost","protocol":"https"}]


Happy coding!

1 Like

Thanks! This is pretty cool. I assume you do this with recipes stored in a database as well?

Yep, a database would work here too! You would just use a database select node instead of the file input – you might have to tweak the function block depending on how your data is stored, but the flow would be very similar overall.