Write to UDT property with tag change script

I am developing a UDT for a weigh scale that reports its units as an integer. So there is an OPC tag called ReportedUnits which is an integer, interpreted as follows:

0 = g
1 = kg
2 = t

I have an expression tag simply called Units that converts this integer into the relevant string:

switch({[.]ReportedUnits, 0, 1, 2, "g","kg","t","")

I would then like to bind this “Units” tag to the EngUnit parameter of all other weight-related values within my UDT, so that any time I see a weight, it’s shown with the units that the weigh scale is actually using. However, as I understand it, I can’t directly bind a UDT member parameter to another UDT tag value (please correct me if this is possible as it will solve my problem in a far easier way).

I know that I can bind a UDT member parameter to a UDT parent parameter, so if I had a UDT parameter “Units”, I can easily bind all my weight value EngUnit to that. But then, I just need to make sure that the UDT parameter updates if my weigh scale units change.

I found another thread that mentioned I can write to a UDT parameter, but did not specify how. I assume I need to use system.tag.writeBlocking but I can’t work out what my path would be. Can anyone help me out with the path (or an easier way to achieve my end goal)?

Easiest way to get the parameter path is to drill into the UDT with the tag browser, right click the parameter and select copy path.
image

Thanks, but this is within a UDT definition so it needs to be dynamic - I need to work out how to extrapolate the path to the parent UDT parameter from a UDT element tag change script.

You would need to build the string then, If you have good organization and naming conventions that shouldnt be too bad.

Yes, that's what I'm seeking some assistance with

We create expression tags in all of our UDTs to expose meta information.
Just add an expression tag called MetaTagPath and set its Expression to {PathToParentFolder}

1 Like

Thanks, that’s a great starting point. I wasn’t aware of the {PathToParentFolder} property!

I was able to get my UDT path from just using the parent folder in the following way.

With that said, I was not able to update a UDT using scripts like I can with tags using [system.tag.configure]. (system.tag.configure - Ignition User Manual 8.1 - Ignition Documentation) my goal was to change the following:
• Value Source
• read/write expressions
for each instance in the UDT. If you figured that out please let me know.

def runAction(self, event):
	# Main UDT path E.G: "[default]_types_"
	path = "[default]_types_"
	
	# browse the folder and all follder/UDTs but stop/ print out only, when tagType = "UdtType"
	configs = system.tag.browse(path, filter = { "tagType": "UdtType","recursive":True})
	
	# get the path for the UDT, from here we can go into each UDT instance and form other scripts. 
	for tag in configs.getResults():
		udtPath = tag['fullPath'])
               # do some other stuff with the path

I got this to work by adding a member to the UDT definition that holds your ReportedUnits INT and define a tag change script on that. Use it directly as an INT and do the EngUnits assignment in the script, rather than using an expression.

	if currentValue.value == 1:
		unit = "kg"
	elif currentValue.value == 2:
		unit = "t"
	else:
		unit = "g"
		
	system.tag.writeAsync("[.]Value.EngUnit", unit)

EDIT: This is on the Root Node and not the members. My alarm part was for the members only. I think due to this I am missing something when I pass my new parameters to the the UDT.

Coming back to this. In my past case I got this to work for the alarms based on the documentation - system.tag.configure

I was able to add new alarms to to the UDT definitions, and by using "m" it only changed premade alarms if they were different or if it was not there it would add the a new alarm to the UDT.

That said. I can not replicate this on parameters for some reason. I make a dicts with keys = to a word and the value equal to "". I tried to copy the format in the documentation and even tried to copy json download file's format. In both case the UDT definition does not update at all.

Any insight would be greatly appricated

Here are the variable values in my code:

UDTDefinitionPath = component value of the folder teh user wants to start in
basePath = [default]_types_/folder
UDTName = the Name of theUDT
UDTconfigs = all the configs of that UDT
collisionPolicy = "m"
# this next var gets made based on a comma seperated list provide by user. E.G:
parms = {'SAP ID': {'dataType': 'String', 'value': ''}, 'Equip Type': {'dataType': 'String', 'value': ''}, 'EDM Key': {'dataType': 'String', 'value': ''}}

With that here is the code, which mimics my alarm update code that works, but for some reason I feel like I am missing something. Or this just can not be done like alarms can:

def runAction(self, event):
		def updateTagParms(basePath, UDTName, UDTconfigs,  collisionPolicy, newParmsmap):
			"""Add new perameters to UDT definitions"""
			parms = {}
			for key in newParmsmap:
				parms[key] = {
				              "dataType": "String",
				              "value": ""
				            }
			system.perspective.print(parms)
			# Configure the list of Tags. Need to pass a list as an argument.
			tagList = [
			            {
			                "parameters":parms,
			                "name":UDTName
			            }
			        ]
			
			system.tag.configure(basePath, tagList, collisionPolicy)
			
			

		# Variables
	    #  merge, modifying values that are specified in the definition, without impacting values that aren't defined in the definition. Use this when you want to apply a slight change to tags, without having to build a complete configuration object.
		collisionPolicy = "m"
		# Parameters to add: TODO: need to take oput spaces in entries to be sure there are no duplicate add ons.
		newParms = str(self.getSibling("ParmsToAdd").props.text).split(",")
		newParmsmap = map(str.strip, newParms)
		
#		#grab MAin Folder path to UDT Instances
		UDTDefinitionPath = self.getSibling("DefinitionPath").props.text
		# had to use broswe due to getting configs from a folder path will not give us a fullpath but instead just teh 'path' that is the UDT name only
		UDTFolderBrowse = system.tag.browse(UDTDefinitionPath, filter = {"tagType": "UdtType", "recursive":False})
		
		for udtDefinition in UDTFolderBrowse:
			# grab the UDT path from teh broswe so we can feed it into the get configure function - This is so we can acess teh parameters
			UDTBrowsePath = udtDefinition['fullPath']
			# get more proprties/parameters for each UDT to be updated	
			UDTconfigs = system.tag.getConfiguration(UDTBrowsePath, False)
			# itterate through each UDT configs
			for udt in UDTconfigs:
				# grab our UDTPath and UDTName from teh configs - To be sure Ignition doesn not get confused on what we are trying to do: update parameters
				basePath = str(udt['path']).replace("/"+str(udt['name']),"")
				UDTName = udt['name']
				
				updateTagParms(basePath, UDTName, UDTconfigs, collisionPolicy, newParmsmap)

EDIT: This code now works. All i did was step away for a bit, ignition had to restart due to computer going to sleep. I have no idea why it wouldn't have worked in the first place. Any insight on this would be great.

I updated my code to mimic the parameter property change. Even this does not update the Root Node UDT definition itself.

I have looked at other posts going back to 2020 and haven't found any updates on scripts to update a UDT definition's root node per say. Some places are:

  1. Edit UDT Definition in script
  2. Scripted editing of UDT definitions - modifying alarm names - I was able to do this to the UDT definition members, yet the Root node I have not figured out a way to script any property/parameter changes

My new code, does the same thing as my previous post but in a different way. No matter if I use a custom made Dict or just the value of "test" the UDT does not seem to update the Root Node UDT definition.

	def updateTagParms(newParmsmap):
			"""Add new perameters to UDT definitions"""
			parms = {}
			for key in newParmsmap:
				parms[key] = {
				              "dataType": "String",
				              "value": ""
				            }
			return parms
			
			
			
	system.perspective.print("Proccess started")
	# Variables
    #  merge, modifying values that are specified in the definition, without impacting values that aren't defined in the definition. Use this when you want to apply a slight change to tags, without having to build a complete configuration object.
	collisionPolicy = "m"
	# Parameters to add: TODO: need to take oput spaces in entries to be sure there are no duplicate add ons.
	newParms = str(self.getSibling("ParmsToAdd").props.text).split(",")
	newParmsmap = map(str.strip, newParms)
	parms = updateTagParms(newParmsmap)
	
	
	#grab MAin Folder path to UDT Instances
	UDTDefinitionPath = self.getSibling("DefinitionPath").props.text

	UDTconfigs = system.tag.getConfiguration(UDTDefinitionPath, True)
	basePath = UDTconfigs[0]['path']
	# itterate through each UDT configs
	for udt in UDTconfigs[0]['tags']:
		# Make sure we are only getting teh UDT definition and not any Atomic tags
		if str(udt['tagType']) == "UdtType":
			# itterate through each element in teh dict to add to the UDT
			for key, value in parms.items():
				# updates teh current parameters
				udt['parameters'][key] = value

			# sends new ipdates to ignition to make teh changes in the  system 
			system.tag.configure(basePath, udt, collisionPolicy)
	
	system.perspective.print("Proccess Finished")

UPDATED CODE USING CHAT GPT - Also added the browse function this morning, this will allow use to start at a root folder and work our way down to a UDT definition or tagType = UdtType:

def runAction(self, event):
	try:
		system.perspective.print("Proccess started")
		# Variables
	    #  merge, modifying values that are specified in the definition, without impacting values that aren't defined in the definition. Use this when you want to apply a slight change to tags, without having to build a complete configuration object.
		collisionPolicy = "m"
		# Parameters to add: Strips all white space from entries and splits each entry into a list
		newParms = [param.strip() for param in str(self.getSibling("ParmsToAdd").props.text).split(",")]
		for element in newParms:
			for char in element:
				if ord(char) > 127:
					raise ValueError("ASCII chars are presents in parameters.")
					
		if newParms[0] == "":
		    raise ValueError("No parameters were provided.")
		# Creates a dict based off teh above list. Each Item in teh list is a key and teh value is "dataType": "String", "value": ""
		parms = {key: {"dataType": "String", "value": ""} for key in map(str.strip,newParms)}
		
		#grab MAin Folder path to UDT Instances
		UDTDefinitionPath = self.getSibling("DefinitionPath").props.text
		
		# need to use browse- Due to if we want to start at teh base folder and drill down this is where we need to start
		# If we start with teh dirct tag/folder we can start with getconfigs function instead
		UDTBrowse = system.tag.browse(UDTDefinitionPath, filter = {"tagType": "UdtType", "recursive":True})
		
		for udt in UDTBrowse:
			# grab the full path of teh UDT definition
			UDTPath = udt['fullPath']
			# we need to have the folder this UDT is in to update teh configs
			basePath = str(udt['fullPath']).replace("/"+ str(udt['name']), "")

			# we just need the configs of teh UDT definition and not the tags uinder them - So we use False as the recursion value. 
			UDTconfigs = system.tag.getConfiguration(UDTPath, False)

			
	#		# itterate through each UDT configs
			for udtConfig in UDTconfigs:#[0]['tags']:
				# Make sure we are only getting the UDT definition and not any Atomic tags
				if str(udtConfig['tagType']) == "UdtType":
				
					# itterate through each element in the dict to add to the UDT
					for key, value in parms.items():
					
						# updates teh current parameters
						udtConfig['parameters'][key] = value
		
					# sends new ipdates to ignition to make teh changes in the  system 
					system.tag.configure(basePath, udtConfig, collisionPolicy)

		system.perspective.print("Proccess Finished")
	except Exception as e:
		system.perspective.print("Process failed with error: " + str(e))