Two ways to loop dynamic write objects for Opto nodes

If you check the help information for groov or PAC nodes, you see that there are specific JavaScript object properties you can define to dynamically write values to your Opto tags. In this post I’ll use groov View nodes, but the technique applies to PAC nodes as well.
Here is an example object that the groov View nodes can write from:

msg.payload = {
  tagName         : 'MyTable',
  tableStartIndex : 5,
  value           : 10
};

Sometimes the tag name will always be the same and you can just set the static property in the node itself, but other times the tag name will change by a simple count value, or sometimes you’ll have totally different names you want to write for. (I’ve already covered writing to groov tables in a previous post here: Writing a groov table with a single node)

To use either of the methods I’ll be going over in this post you’ll need to change the groov write node “value” property to be msg.payload.value since that is the property that we’ll be defining and sending from the function node. The rest is handled by the for loop and node.send() function.

image


For the first example I’ll be writing to groov View tags test_value01 through test_value16 with some msg.payload array that could come from any Node-RED data source – in this case an inject node:

image

Here is the contents of the for loop function node:

for (i = 0; i < msg.payload.length; i++) {
    twoDigit = ('0'+(i+1)).slice(-2);
    
    node.send({ payload : {
        tagName         : 'test_value' + twoDigit,
        value           : msg.payload[i]
    }})
}

Here the incoming data is held in the individual msg.payload[i] array slots. You could also have it on a sub-property, in which case you could just change the node.send() value property to be msg.payload[i].subProperty, or wherever it happens to be stored.

The main part of this example is the 'test_value' + twoDigit I’m using for the tagName. Since each tag name consists of the string ‘test_value’ and then a two-digit number '01' through '16' we need to add a leading '0' to all single-digit values, but avoid having adding it to two-digit values so we don’t end up trying to reference test_value011, which is not one of our tags.
To do this we simply add 1 to the index i (since the for loop starts at the integer 0 but our first test_value is 01), then append the character '0' to the front for our leading 0, and finish by taking only the last two values to prevent creating any triple-digit values once we pass i+1=10.
This is how we get the line twoDigit = ('0'+(i+1)).slice(-2);

Note: Make sure you change the msg.payload[i] value to wherever your data source is, and the msg.payload.length to the data table length in the for loop definition.

Here’s the flow import text for you to try out for yourself:

[{"id":"d3ecf84d.34d258","type":"inject","z":"6dc3f358.26a64c","name":"data source","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"alphabet","payload":"[\"A\",\"B\",\"C\",\"D\",\"E\",\"F\",\"G\",\"H\",\"I\",\"J\",\"K\",\"L\",\"M\",\"N\",\"O\",\"P\"]","payloadType":"json","x":550,"y":680,"wires":[["d1c4231a.de17b"]]},{"id":"cbfaf03d.2cec3","type":"debug","z":"6dc3f358.26a64c","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":640,"wires":[]},{"id":"d1c4231a.de17b","type":"function","z":"6dc3f358.26a64c","name":"for loop","func":"for (i = 0; i < msg.payload.length; i++) {\n    twoDigit = ('0'+(i+1)).slice(-2);\n    \n    node.send({ payload : {\n        tagName         : 'test_value' + twoDigit,\n        value           : msg.payload[i]\n    }})\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":720,"y":680,"wires":[["cbfaf03d.2cec3","13ae212e.9ff38f"]]},{"id":"13ae212e.9ff38f","type":"groov-write-ds","z":"6dc3f358.26a64c","dataStore":"b1060423.ddf178","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"","x":910,"y":680,"wires":[[]]},{"id":"b1060423.ddf178","type":"groov-data-store","z":"","project":"dc976ab5.a5fcc8","dsName":"nodered"},{"id":"dc976ab5.a5fcc8","type":"groov-project","z":"","address":"localhost"}]

The other scenario you might have is that there is no sequential way to reference each of the tag names, for example you might have unique names for each and every tag you want to write, but still want to fill them with a table of values. For example your tags could be:

In this case we cannot dynamically create the tag name by adding a number to a string, we need to reference the string names individually. To do this, just create an additional array to hold each tag name, then reference each array index just like you do with the incoming data values:

// List out the tagnames in a reference array:
tagNames = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth"];

for (i = 0; i < msg.payload.length; i++) {
    node.send({ payload : {
        tagName         : tagNames[i],
        value           : msg.payload[i]
    }})
}

Here we just create a tagNames array with the unique, individual names, then reference them with tagName : tagNames[i], in the node.send() function.

Again, you will need to set the for loop size based on whatever your data array is, in this case msg.payload.length, and make sure that your tagNames list has the same number of items as that data array, and you should be good to go.

Here is the flow import text for this second example:

[{"id":"e1e14225.fe757","type":"function","z":"6dc3f358.26a64c","name":"for loop","func":"// List out the tagnames in a reference array:\ntagNames = [\"first\", \"second\", \"third\", \"fourth\", \"fifth\", \"sixth\", \"seventh\", \"eighth\"];\n\nfor (i = 0; i < msg.payload.length; i++) {\n    node.send({ payload : {\n        tagName         : tagNames[i],\n        value           : msg.payload[i]\n    }})\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":720,"y":860,"wires":[["cd40743f.ee5978","b21c333d.c80a6"]]},{"id":"3e5164fc.8e1c1c","type":"inject","z":"6dc3f358.26a64c","name":"data source","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"values","payload":"[\"value1\",\"value2\",\"value3\",\"value4\",\"value5\",\"value6\",\"value7\",\"value8\"]","payloadType":"json","x":550,"y":860,"wires":[["e1e14225.fe757"]]},{"id":"cd40743f.ee5978","type":"debug","z":"6dc3f358.26a64c","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":820,"wires":[]},{"id":"b21c333d.c80a6","type":"groov-write-ds","z":"6dc3f358.26a64c","dataStore":"b1060423.ddf178","tagName":"","tableStartIndex":"","value":"payload.value","valueType":"msg","name":"","x":910,"y":860,"wires":[[]]},{"id":"b1060423.ddf178","type":"groov-data-store","z":"","project":"dc976ab5.a5fcc8","dsName":"nodered"},{"id":"dc976ab5.a5fcc8","type":"groov-project","z":"","address":"localhost"}]

If you have any questions about these flows or the methods used, or if you use these in your own project and modify them in some way, please feel free to ask questions and share your projects in the thread below!

Happy coding!

1 Like

I don’t believe there is, but is there a way with the PAC read/write notes to dynamically specify the tag type in addition to the tag name? With a standard naming convention, this would be super helpful to me.

msg.payload = {
    tagName: i_SomeIntegerValue,
    tagType: "Int32 Variable"
};

Being able to pass that payload to the PAC read node would be great. Similarly, being able to add a value and pass it to PAC write would be great:

msg.payload = {
    tagName: i_SomeIntegerValue,
    tagType: "Int32 Variable",
    value: 237
};

I’ve essentially made a subflow to do this, but it would be nicer if it were built-in.

@varland that is correct, there’s no way to set the data type dynamically. One solution could be to use multiple write nodes, one for each data type that you need, and have them all wired into a switch node that uses another property to determine the tag type and re-route each message depending on where you need it to be written – that should give you the functionality you need with very little performance overhead, and not too many extra nodes in your flow.
A subflow is a great solution but I will get a ticket in so this could be added to the node by default.

Have you tried:

msg.payload = {
    tagName: i_SomeIntegerValue,
    dataType: "int32-variable"
};

Edit: Nope, that don’t work. Would be a simple change to the node to make that work though.

@torchard: that’s essentially what I did in my subflow. If it gets added as an option in the default node, great. If not, I have a workaround. Just wanted to make sure I wasn’t missing something as I’m very new to Node-RED.

@philip: Thanks for the suggestion. I tried many variations of this, but obviously couldn’t get it working until I went the subflow route.