Running into issue with derived tag expression

We're planning to use derived tags to represent enumerated tags. We have a folder called "EnumTables" under the tag provider root and it contains Dataset tags to represent the translation table. Column 0 is the raw number and column 1 is the string representation. We want to store the EnumTable tag name in a custom "EnumTable" property on our derived tag so we can use it in other contexts and maintain it in one place.

I'm trying to use this expression:

lookup(
  tag(concat("[~]EnumTables/", {[.]Run_Status 1.EnumTable})),
  {source},
  "Not Found",
  0,
  1
)

however the tags value keeps resolving to "1".

If I change the tag provider to:

tag(concat("[OhioDemoTags]EnumTables/", {[.]Run_Status 1.EnumTable}))

it works correctly. Am I missing something?

I would move this over to a UDT def and have the table path be a parameter on the UDT itself.

We went down the route of UDTs but have since converted to raw tags as we found UDTs too constraining.

Maybe post how you have EnumTable laid out and where your tags are located.

Edit: Misleading. [~] goes to the root. [.] goes relative to the tag

You could try this syntax and see if it gets you closer as I think it's a bit clearer. Note the write expression makes it bi-directional which you may not want for a run_status.

On a side note using UDT's is probably going to make your life a lot easier if you use them correctly. Might be worth still looking into that.

image

Maybe I misunderstood this from the docs.

`[~]` | Relative to the Tag Provider of the tag that is being bound (root node).

[~] refers to the Tag's Provider root. It replaces an explicit provider name and thus protects the tag path from "breaking" if the provider is renamed or if the tag is imported/exported/moved between different providers.

Whoops, I edited my comment. [.] is relative, [~] is root.

This syntax still works for what you want to do though:

1 Like


The correct output should be "1: Running" and yet I'm just getting "1".

I'm trying to dynamically build the tag using the custom property as well hence the

tag(concat("[~]EnumTables/", {[.]Run_Status 1.EnumTable}))

If you want to avoid UDT's but still retain some scalability consider a centralized Gateway Tag Change script. This way you get a few more tools (like custom error logging/handling) and you can somewhat limit the places where you will have to make the inevitable changes.

Add a custom string to each tag called enum:

Then add a single Tag Change script for all the similar devices. Something like this:

def onTagChange(initialChange, newValue, previousValue, event, executionCount):
	import traceback
	logger = system.util.getLogger("EnumTestTagChangeScript") 
	try:
		enumList = ["Burger","Taco","Sushi","Pizza","Soup"]
		enumTagPath = str(event.tagPath) + ".enum"
		tagValue = event.currentValue.value

		enumString = enumList[tagValue]
		
		system.tag.writeBlocking([enumTagPath], [enumString],10)
	
	except:
	    system.tag.writeBlocking([enumTagPath], "Error",10)
	    message = traceback.format_exc() 
	    logger.error(message)

Then add the tags:

The other bonus is it will cut your tag count in half.

1 Like

This would be nice if you didn’t have to manually add each tag to the script which it looks like you have to. That also won’t scale nicely when you’re bringing on an entire new CTB.
We poll a raw numeric value from a field device and then display the enumerated value to operators.

Converting status integers from tags into corresponding strings is not a job for tags. This belongs in your user interface. See this entire topic:

In that case it is runScript() in expression tags, where here it is scripted lookups, but it is the same fundamental design error.

3 Likes

By doing it on your user interface, wouldn't every screen in user interface need to check if a tag needs to be enumerated?

Yes. If you have thousands of tags and only dozens of clients, it's a win to have the UI do it. While enabling internationalization at a later time.

That seems inefficient and cumbersome. Every UI component, from a text box to a grid cell, will need to constantly look if the tag value it's representing needs to be enumerated.

Why?

Make embeddable views or templates based on the expectation of what they will display. The .enum custom property should only be indirect bound in UI components that expect it.

2 Likes

I can predict that you will be asking “Why is my application so slow and need so much expensive RAM” a year from now. We have all dealt with scaling issues.

With thousands of tags, efficient processing at scale only has so many tools you can throw at it, because the tag count is what it is.

You have been given many suggestions for better tag processing, like using UDT’s and only doing work if the value changes.

Phil has also suggested building an efficient UI, which is arguably the most important step in the design process.

Ignition has many incredible UI tools which, when used correctly with a UDT and efficient tag processing, scales incredibly well.

You really should take a hard look at other tools to accomplish your goals.

1 Like

I care less about the UI and more about our data pipeline that gets data out and set points back into Ignition from our internal applications.

I can’t explain why your [~] relative reference is breaking.

Ignoring the good advice to translate as much as possible in the UI (even if POU is outside Ignition)…

Side note: I would put the entire path to the enumeration table in the custom property, instead of just a part of the path. I would want the flexibility to look at the enum table wherever it exists in the tag database, be able to utilize relative referencing ([.], [~], etc.), and not be confined to a single folder of a provider. Additionally, I typically prefer an array tag, except for cases where large gaps in the enum table exist (an Engine Annunciator Fault Code table, for example). I would not utilize any script to perform this transform, regardless of UI-based or tag-based solution. I would also use an expression tag, not a derived tag, as I can’t envision a reason to ever write a value back to this (or the source) tag. To each-their-own here.

See below for a good reference to options for building tag paths within expressions:

Near the bottom, you’ll see the syntax to reference a custom property of ‘this tag’ (hereon, ‘this’ will refer to a tag that is intended to contain the resultant string post-lookup):
{this.name}

To retrieve your custom property from ‘this tag’:
{this.myCustomProperty}

To have access to the enum table (assuming its entire path is contained in the value of ‘myCustomProperty’), wrap the value of the prop in a tag() expression:
tag({this.myCustomProperty})

To access a specific row of the array tag:
tag({this.myCustomProperty})[<row_number>]

If using another tag’s value (in this example, the tag’s name is ‘EnumValue’, residing in the same folder)
tag({this.myCustomProperty})[{[.]EnumValue}]

To access a specific row & column of a dataset tag:
tag({this.myCustomProperty})[<Row_Index>, <"Column_Name" -OR- Column_Index>
tag({this.myCustomProperty})[{[.]EnumValue.value}, "Text"]

Or, if there are gaps in your dataset, and you must use lookup:
lookup(tag({this.myCustomProperty}), {[.]EnumValue.value}, “ERROR”, "Value", "Text")
You can also substitute column indexes if desired: “Value” –> 0, and “Text” –> 1.

With the above, a ‘myCustomProperty’ value of either ‘[~]…’ or ‘[]…’ should function as expected.