System.tag.configure incorrectly overriding multiple UDT tag properties

I need a quick way to programmatically override properties on certain tags inside a UDT. For instance, setting a custom description for multiple iterations of the same device. The solution I’ve come up with is to read the tag configuration using system.tag.getConfiguration and then search through the result and modify the ‘value’ property for the tag in question. Then I pass the modified configuration back into system.tag.configure. I’m able to successfully change the value I want, however, it seems that when I write the modified configuration, other properties on the tag are showing as overridden. The values still match the UDT definition but it doesn’t seem like this is right. Appreciate any ideas on how to prevent unintentionally overriding extra tag properties.

Simplified version of my script (I just hardcoded the property modification to keep the post shorter)

folder = "[default]CP1/Test Folder"
updateTagName = 'UDT Tester'
nodes = system.tag.getConfiguration(folder + '/' + updateTagName, True)

nodes[0]["tags"][0]["tags"][1]["value"] = "Divert 1"
system.tag.configure(folder,nodes)

Multiple properties show as overridden even though I’m only changing the Value property

I’m also seeing this happen in 8.3.1

Where can I download this version from? I’m sure IA would be interested to know as well! :laughing:

I have seen this happen as well in later versions possibly even as late as 8.1.5 although I can’t be sure… I noticed it recently when I found a UDT’s tags had unexpectedly different config from the definition; I can’t remember exactly, but from memory the data type didn’t match. Then I noticed that the UDT instance config was overridden which I thought was strange as I wouldn’t normally do that.
Good to know, and certainly seems like a bug!

@PGriffith

Haha… 8.1.3 :man_facepalming:

I have seen this same issue too still in 8.1.9. It would be nice to see this fixed as I assume that once it is flagged as overridden it will no longer update if you change the parent UDT.

I am still seeing this problem in 8.1.13…
This is a pretty bad bug in my mind as we can’t reliably configure tags to update just one property, without potentially screwing over an entire UDT.
For instance in my UDT i have three expression tags. I want to simply change the expression so that it overrides what the parent UDT has by default. But in doing so, i am getting all sorts of unrelated properties being overridden.

rootPath = "[default]_types_/Basic/Real"
configs = system.tag.getConfiguration(rootPath, True)
###print configs[0]["tags"]
for tag in configs[0]["tags"]:
	###print tag['name']
	if tag['name'] == 'Active':
		print tag['name']
		print tag['expression']
		# Set new expression
		tag['expression'] = "{[.]Value/Alarms.HasActive}"
		
# end for


# Write changes to config definition back to UDT definition
print system.tag.configure(rootPath,configs[0]["tags"],"m")

This yields the following results where not only the “Expression” has been overridden, but so has all sorts of other properties for the tag “Active”.

As a workaround, you could ONLY programmatically update the properties that you are changing (or have already been overridden with a value different than what is configured on the UDT definition itself). This is what I typically do:

tagPath = '[default]some/path/to/udt/tag'
parentPath = str(tagPath).rsplit('/',1)[0]
tagConfig = dict(system.tag.getConfiguration(tagPath, True)[0])
udtPath = '[default]_types_/path/to/udt/tag'
udtConfig = dict(system.tag.getConfiguration(udtPath, True)[0])
del tagConfig['path']	# Tags seem to break if you accidentally write back to the path property
for key in tagConfig:
	if tagConfig[key] == udtConfig.get(key, '') and str(key) != 'name':
		del tagConfig[key]
tagConfig['propertyToBeUpdated'] = 'newPropertyValue'
system.tag.configure(parentPath, tagConfig, "o")

It’s not the ideal solution, but it gets the job done for me.

2 Likes

Interesting… thanks i’ll have a play.
So this is actually configuring a UDT instance right?
Currently i’m trying to configure the UDT itself but i will also need to do this at some point soon.

Are you effectively just stripping out all tagConfig keys except the ‘name’?

Yes, in my example post I was assuming that ‘some’ would be an instance of UDT ‘path’ and ‘to/udt/tag’ would be a folder structure within the UDT where ‘tag’ is the actual tag being configured. This example should still apply if you were modifying a UDT with another parent UDT. the tagPath property in the above example would just be something like ‘[default]types/childUDT/tag’ while udtPath would be something like ‘[default]types/parentUDT/tag’

And yes, I am effectively stripping out all the keys of the tag configuration besides ‘name’ (which is needed to determine which tag is being configured), the key of the property you wanted to update, and any other tag properties whose values differ from the parent UDT’s. If you wanted to revert the child UDT back to the parent UDT’s tag properties entirely, your tagConfig would only consist of the name property.

1 Like

I managed to achieve what i needed by defining only the expression tags i wanted in a totally separate array.

alarmTags = [  
	{
	  "expression": "some expression here",
	  "name": "Active",
	  "tagType": "AtomicTag"
	},
    {
      "expression": "some expression here",
      "name": "Acked",
      "tagType": "AtomicTag"
    },
    {
      "expression": "some expression here",
      "name": "Alarm",
      "tagType": "AtomicTag"
    },
    {
      "expression": "some expression here",
      "name": "AlarmPriority",
      "tagType": "AtomicTag"
    },
]

print system.tag.configure(rootPath,alarmTags,"m")	

Then using that in system.tag.config. That also seems to only override the destination tags that match, and not affect anything else in the UDT.

Maybe thats what IA intended us to do, but i’m still getting my head around this new JSON tag structure and the new config functionality.

I am playing around with changing properties programmatically, most of which are instances of UDTs. If I change a tag property, something like this,

tagPath = 'myPath'
parentPath = str(tagPath).rsplit('/',1)[0]

props = [
            {
                'name': 'Tag1', 
            	'value': 111
            }
        ]
        
tags = [
			{
				"tags":props,
				"name":"New Instance"
			}
		]

print system.tag.configure(parentPath,tags,"m")

I only see the property value for Tag1 get set to override, which is what I want.

However, if I do something similar for alarms,

tagPath = 'myPath'
parentPath = str(tagPath).rsplit('/',1)[0]

alarms = [
            {
                'name': 'Alarm1', 
                "setpointA":10,
                "label":'i changed the label'
            }
        ]
        
tags = [
			{
				"alarms":alarms,
				"name":"Alm1"
			}
		]

print system.tag.configure(parentPath,tags,"m")

it sets the entire alarm to override. Is this normal? Fixable?

I’m using 8.1.17.

From what I have tested in 8.1.10 through 8.1.17, this is normal. Obnoxious, but normal.

And nothing really that we can do to side step the obnoxiousness? :slight_smile:

Not that I have found. Maybe IA has some tricks up their sleeves though? @PGriffith

Travis Cox responded on the side and told me this is the way until perhaps 8.2 when they go to storing tags in file system. Appreciate the clarification on this.

2 Likes

Interesting. I wonder how this will be done? just a massive json file? :S I wonder how fast this will be especially for larger tag databases

Not sure. I’m not sure it will be implemented, but they seem to think it’s possible where as right now it isn’t.

FWIW, I recently forgot about this and inadvertently added property overrides to a large number of UDTInstance tags :confused: So I created a script to remove the prop overrides on tags part of UDT instances that were not included in a list of property names.

The script copies the tag json back into clipboard, so you can review it first before saving as a json and importing it back in.

Main Functions
def removePropOverrides(tags, retainProps=None, tagpath='', insideUDTInstance=False):
	'''
	This will remove property overrides set on tags that are within UDTInstances that are not
	contained within the predefined retain set, or the retain set passed into the function.
	
	Revision History
	===========
	Rev	Date		Author			Comment
	1.0	2022-10-20	Nick Minchin	Original
	
	
	Args
	===========
	tags:				- tag json e.g. copied into the clipboard by using the "Copy JSON" context menu option from the Tag Browser
	retainProps:		- a set with a list of properties to retain, for example {'writePermissions'}
	tagpath:			- INTERNAL USE ONLY. Keeps track of the relative tag path used to print the properties removed along with the tags they were removed from.
	insideUDTInstance:	- INTERNAL USE ONLY. Keeps track of whether or not the current `tags` are within a UDT instance or not. This will determine if tag props 
						  will be removed or not.
						  
	Return
	===========
	Returns the modified tags as a Py object of dicts and lists
	'''
	fn = 'removePropOverrides()'
	this = removePropOverrides
	if isinstance(tags, str) or isinstance(tags, unicode):
		tags = system.util.jsonDecode(tags)
	
	if retainProps is None:
		retainProps = {}
	if not isinstance(retainProps, set):
		retainProps = set(retainProps)
	
	# these props should always exist and should not be removed!
	retainProps.add('name')
	retainProps.add('tagType')
	retainProps.add('tags')
	retainProps.add('value')
	retainProps.add('parameters')
	retainProps.add('typeId')
	
	# remove all props not in the retain list, if the tag is within a UDTInstance
	if isinstance(tags, dict):
		if insideUDTInstance:
			removedKeys = []
			for key in tags:
				if key not in retainProps:
					removedKeys.append(key)
					del tags[key]
			
			if len(removedKeys) > 0:
				print 'Tag: "{}", Removed keys: {}'.format(tagpath, system.util.jsonEncode(removedKeys))
		
		# check if the tags object has child tags and run the function over these as well
		if 'tags' in tags:
			for tag in tags['tags']:
				insideUDTInstance_ = insideUDTInstance or 'typeId' in tag
				this(tag, retainProps, tagpath + '/' + tag['name'], insideUDTInstance_)
	return tags

def removePropOverridesFromClipboard(retainProps=None):
	'''
	This is a wrapper for removePropOverrides which passes in tag json stored in the clipboard.
	'''
	tag_json = shared.util.clipboard.readText()	
	tags = removePropOverrides(tag_json, retainProps)
	
	return tags

tags=removePropOverridesFromClipboard({'writePermissions'})
shared.util.clipboard.writeText(system.util.jsonEncode(tags))
Clipboard Functions
from java.awt.datatransfer import StringSelection
from java.awt.datatransfer import Clipboard
from java.awt import Toolkit 

def setup():
	global toolkit, clipboard
	
	toolkit = Toolkit.getDefaultToolkit()
	clipboard = toolkit.getSystemClipboard()	

def writeText(text):
	setup()
	clipboard.setContents(StringSelection(text), None)
	
def readText():
	setup()
	from java.awt.datatransfer import DataFlavor
	contents = clipboard.getContents(None)
	return contents.getTransferData(DataFlavor.stringFlavor)

For example, the output print statements will return something like below. If you don't get anything printed, then no tags are affected.

Example Print Statement Output
Tag: "/Folder 1/Instance/Sec", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 1/Instance/Month", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 1/Instance/Day", Removed keys: ["dataType","opcServer","valueSource"]
Tag: "/Folder 1/Instance/Year", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 1/Folder 2/Instance/Day", Removed keys: ["dataType","opcServer","valueSource"]
Tag: "/Folder 2/Instance/Day", Removed keys: ["dataType","opcServer","valueSource"]
Tag: "/Folder 2/Folder 1/Instance/Month", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 2/Folder 1/Instance/Day", Removed keys: ["dataType","opcServer","valueSource"]
Tag: "/Folder 2/Folder 1/Instance/Year", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 2/Folder 1/Instance/Sec", Removed keys: ["deadband","opcServer"]
Tag: "/Folder 2/Folder 1/Folder 2/Instance/Day", Removed keys: ["dataType","opcServer","valueSource"]

And if not obvious, use at your own risk. Check the output before actually importing anything! (and take a backup of your tags in case you need to revert)

2 Likes

I was having the same issues when trying to update tag configurations for UDT instances. Below is my temporary fix. This is the same tag configuration JSON you would get by right clicking the tag and selecting "Copy JSON". I usually use the result from getTagConfigObj() to make changes to the configuration. Then convert it back to a JSON string with system.util.jsonEncode(). Finally, I use system.tag.configure() to update the UDT instance. Definitely not the most efficient solution since it requires reading/writing to IO, but works for me.

def getTagConfigStr(tagPath):
	filePath = system.file.getTempFile('.json')
	system.tag.exportTags(filePath, [tagPath])
	tagConfigStr = system.file.readFileAsString(filePath)
	
	return tagConfigStr


def getTagConfigObj(tagPath):
	return system.util.jsonDecode(getTagConfigStr(tagPath))	

Update: Thanks to FrankP, no IO required!

def getTagConfigStr(tagPath):
	return system.tag.exportTags(tagPaths = [tagPath])
	
def getTagConfigObj(tagPath):
	return system.util.jsonDecode(getTagConfigStr(tagPath))	
2 Likes

Nathan,

From the manual, it looks like system.tag.exportTags will return a return a string as a tag export if the file path is omitted. Perhaps can use that with the jsonDecode and get the same result with less file IO? I might play with this later.

-Frank

2 Likes