Reading PLC tags offline

If I don't have a PLC but I have the project, most of our tags are within UDTs which are already developed, so these are fairly easy to create manually or using a tool that takes in a list of instance names and parameters and creates the udt instances for me.

Without a PLC, in v8, you can convert tags between opc and memory without any information being removed (this wasn't in the case in v7).

I use this script, just copied into the script console, to convert tag json copied into the clipboard, between OPC & memory types. Actual memory tags or any other tags won't be affected, only those that were originally OPC will be affected.

Just comment in/out the bottom two lines, then paste back into the tag browser (need to right click, paste - Ctrl+V doesn't work for tags). It works for tags as well as UDT definitions and their tags.

EDIT 2024-07-01

from java.awt.datatransfer import StringSelection
from java.awt.datatransfer import Clipboard
from java.awt import Toolkit
from java.awt.datatransfer import DataFlavor

### clipboard functions ###
def setup():
    global toolkit
    global clipboard
    toolkit = Toolkit.getDefaultToolkit()
    clipboard = toolkit.getSystemClipboard()
def writeClipboard(text):
    setup()
    clipboard.setContents(StringSelection(text), None)
def readClipboard():
    setup()
    contents = clipboard.getContents(None)
    return contents.getTransferData(DataFlavor.stringFlavor)
### end clipboard functions ###


def convertTagSource(obj, valSource='original', insideUdtInstance=False):
    """
    Converts tag JSON between memory and their original value source.
    This works by looking at the tag's valueSource and identifying property values for other value sources to determine
    what the original value source was.
    Note: UDT instance tags do not have a lot of this information, so some of these tags are unnecessarily converted
    to memory tags such as expression tags. This is unavoidable without reading the tag's config using
    system.tag.getConfiguration. But... lazy...
    """
    this_fn = convertTagSource
    if valSource not in ['original', 'memory']:
        raise ValueError('Invalid `valSource` value passed into function. Valid values are "original" or "memory".')
    # if the obj is a list, then run through the list and convert any tags contained
    if isinstance(obj, list):
        for i in range(len(obj)):
            obj[i] = this_fn(obj[i], valSource, insideUdtInstance=insideUdtInstance)

    # if the obj is a dict, check if it is a tag and convert it if required
    if isinstance(obj, dict):
        if 'tagType' in obj and obj['tagType'] == 'UdtInstance':
            insideUdtInstance = True

        if valSource == 'original':
            if obj.get('valueSource', None) != 'expr':
                # convert back 'opc' source tags
                if 'opcItemPath' in obj and obj['opcItemPath'] != '':
                    obj["valueSource"] = 'opc'
                    # remove the overridden 'value' from using a memory source
                    obj.pop('value', None)
                # convert back 'reference' source tags
                elif 'sourceTagPath' in obj:
                    obj["valueSource"] = 'reference'
                    # remove the overridden 'value' from using a memory source
                    obj.pop('value', None)

            if insideUdtInstance and 'valueSource' in obj:
                obj.pop('valueSource', None)
                obj.pop('value', None)

        # if the val_source arg is memory, then convert opc and reference types to memory
        elif valSource == 'memory':
            if (any(key in obj for key in ['opcItemPath', 'sourceTagPath']) and
                    obj.get('valueSource', None) != 'expr') or \
                    (insideUdtInstance and 'tagType' in obj and obj['tagType'] == 'AtomicTag'):
                obj["valueSource"] = 'memory'

                dt = obj.get('dataType', None)
                # set default values for the memory tags, otherwise they're all set to None which is annoying
                if dt in ['Boolean']:
                    obj['value'] = False

                elif dt in ['Float4', 'Int4', 'Float8']:
                    obj['value'] = 0

                elif dt in ['String']:
                    obj['value'] = 'Dev Test'

                elif dt is None:
                    obj['value'] = 0

        else:
            raise TypeError('Invalid val_source argument value! ({})'.format(valSource))

        for key in obj.keys():
            if isinstance(obj[key], dict):
                obj[key] = this_fn(obj[key], valSource, insideUdtInstance=insideUdtInstance)
            if isinstance(obj[key], list):
                for i in range(len(obj[key])):
                    obj[key][i] = this_fn(obj[key][i], valSource, insideUdtInstance=insideUdtInstance)

    # if it's just a flat type, ignore it
    else:
        pass
    return obj

def convertTagJsonSourceFromClipboard(convertTo):
    """ Converts tag json from """
    # make sure you copy some tag json first to the clipboard!
    tagsJson = readClipboard()
    tags = system.util.jsonDecode(tagsJson)

    if convertTo in ['original', 'memory']:
        for item in tags:
            tags[item] = convertTagSource(tags[item], convertTo)

        tags = system.util.jsonEncode(tags)
        tags = tags.replace(":True", ":true").replace(":False",
                                                      ":false")  # json technically doesn't capitalise first letter of bool vals.. compare tools whinge
        writeClipboard(tags)
    else:
        system.gui.errorBox("You failed at setting the value source correctly... use 'original' or 'memory' idiot", 'Idiot')

convertTagJsonSourceFromClipboard('memory')
#convertTagJsonSourceFromClipboard('original')

For Vision as well, to get rid of the red overlays, you can disable overlays in the designer:
image

3 Likes