Create a UDT Definition with Scripting

We regularly use scripting to instantiate UDTs.

However I am build a UDT definition right now for an entire machine in order to make it possible to create multiple instances of the machine by altering a single parameter (PLC connection name). This is possible because the vendor is nice and makes things exactly the same from machine to machine.

Building this UDT however, I feel like it would quicker if I could define the UDT instance from the vendor provided tag sheet, rather than building it by hand. This is because of the volume of tags.

I have no idea if this is even possible so I’m just asking here if someone has done it successfully and could provide details.

At the end of the day, I’m not sure if writing the script would be faster than building the UDT.

Thanks,

Nick

An external python script that read the tag list and spit out the json definition of a UDT? Sounds straightforward. (Not to say easy or quick, but not rocket science.)

OT but I read an interesting quote the other day that basically said rocket science isn’t hard, as it is based on well known math. On the other hand, Rocket Engineering … now that’s hard!

3 Likes

Since I have the UDT partially built, I think this is a reasonable solution to finish it off. I’ll post the solution back here so the next person who comes asking has code to start with.

Thanks,

Nick

@pturmel thanks for the suggestion, it shakes out something like this.

Note: the exact dictionary paths are dependent on your specific UDT. I’m working out of a jupyter notebook because I find that easiest to iterate quickly with.

import json
from pprint import pprint
import pandas as pd

# in order to find this, grab one instance of a tag
def alarmInstance(name, plcTag):
    return {
        'name': name,
        'parameters': {
            'plcName': {
                'dataType': 'String',
                'value': {
                    'bindType': 'parameter',
                    'binding': '{plcName}'
                }
            },
            'plcTag': {
                'dataType': 'String', 
                'value': plcTag
            }
        },
        'tagType': 'UdtInstance',
        'tags': [
            {
                'name': 'value', 
                'tagType': 'AtomicTag'
            },
            {
                'name': 'statusText', 
                'tagType': 'AtomicTag'
            }
        ],
        'typeId': 'WynrightNew/Alarm'
    }


# open the file
with open(jsonFile) as f:
    j = json.load(f)

# This is specific to UDT, string format, etc.
# Key point is to create any folders that do not exist

for i in range(len(plcTags)):
    plcTag, name = plcTags[i], names[i]
    conveyor = name.split("-")[0]
    
    for ii in range(len(j["tags"][1]["tags"])):
        if j["tags"][1]["tags"][ii]["name"] == conveyor:
            try:
                j["tags"][1]["tags"][ii]["tags"].append(alarmInstance(name, plcTag))
            except:
                j["tags"][1]["tags"].append(
                    {
                      "name": conveyor,
                      "tagType": "Folder",
                      "tags": [alarmInstance(name, plcTag)]
                    }
                )

# Finally write to file
with open("DoorACL - UDT Modified.json", 'w') as output:
    json.dump(j, output, indent=4)

This type of method is a huge time saver

Cheers,

Nick

1 Like

Just a little nitpick:

Now, If you need to align another list to the one you’re iterating through, zip is what you need:

for i in range(len(plcTags)):
    plcTag, name = plcTags[i], names[i]

# this becomes:
for tag, name in zip(plcTags, names):

And it makes everything much simpler:

for plcTag, name in zip(plcTags, names):
    conveyor = name.split("-")[0]
    
    for tag in j["tags"][1]["tags"])):
        if tag["name"] == conveyor:
            try:
                tag["tags"].append(alarmInstance(name, plcTag))
            except:
                j["tags"][1]["tags"].append(
                    {
                      "name": conveyor,
                      "tagType": "Folder",
                      "tags": [alarmInstance(name, plcTag)]
                    }
                )

Allows you to get rid of variables named i and such. I’d also suggest finding better variable names than j, especially when they’re used along side iterating variables named i.

If you do NEED to enumerate a list, then there’s still a better way to do it than range(len()). It’s called enumerate, and it works with anything that’s iterable:

for i, tag in enumerate(tags):
    code
for i, (tag, name) in enumerate(zip(tags, names)):
    # tag and name are in parentheses we need to first unpack enumerate's tuple, then unpack zip's one
    code
for (i, tag), name in zip(enumerate(tags, names)):
    code

Python allows for crazy things, especially if you’re like me and come from C. It’s not always super intuitive at first and might need some getting used to, but it does make things significantly simpler in the long run.

3 Likes

Thanks for the tips. What I ended up doing is adding all the folders in advance:

for folder in foldersToAdd:
    jsonData["tags"][1]["tags"].append(
        {
          "name": folder,
          "tagType": "Folder",
          "tags": []
        }
    )

And then there was no more need to have the try/except to account for missing folders:

for plcTag, name in zip(plcTags, names):
    conveyor = name.split("-")[0]
    
    for tag in jsonData["tags"][1]["tags"]:
        if tag["name"] == conveyor:
            tag["tags"].append(alarmInstance(name, plcTag))

If I may add one more thing: try/except should be used for error handling/recovery, not as a way to build logic. Especially when catching everything at once. It’s a good thing you removed it.
In your (old) case, you should use an if statement to check if the folder exists and then act accordingly.