tagChange Script Not Executing When Inside A UDT

Ignition Version 8.1.44
Vision Module

I have this script that if I setup the tags outside of a UDT works great. But when I attempt to run it from inside the UDT it just does not work. I am assuming there is some hidden thing about UDTs that I do not understand. What is supposed to happen is that the tag the script is attached to becomes true, pulls the fault code from another tag, writes to two other tags, then creates an alarm on the original tag that is executing the script.

def _findFault(ds, code):
    try:
        rows = ds.getRowCount()
        for r in range(rows):
            try:
                ds_code = int(ds.getValueAt(r, 0))
            except:
                continue
            if ds_code == int(code):
                return (str(ds.getValueAt(r, 1)), str(ds.getValueAt(r, 2)))
    except:
        pass
    return ("Unknown Fault ({0})".format(code), "No documented solution.")

def _ensureAlarmOnIsFaulted(abs_isfaulted_path, label_text, message_text):
    try:
        changes = {
            "DriveFaultActive": [
                ["enabled",   "Value", True],
                ["priority",  "Value", "High"],
                ["mode",      "Value", "Equality"],
                ["setpointA", "Value", 1],
                ["label",     "Value", label_text],
                ["message",   "Value", message_text],
            ]
        }
        system.tag.configure([abs_isfaulted_path], changes)
    except:
        pass

# --- Tag Change Entry Point ---
try:
    if initialChange:
        return

    newVal = bool(currentValue.value)
    oldVal = bool(previousValue.value)

    isFaultedPath = str(tagPath)
    lastCodePath   = "[.]Last Fault Code"
    descPath       = "[.]Fault Description"
    solnPath       = "[.]Fault Solution"
    datasetTagPath = "[~]Assets/Fault Data/Drives/PowerFlex/PowerFlex 753 Faults"

    if newVal:
        code_qv, ds_qv = system.tag.readBlocking([lastCodePath, datasetTagPath])
        fault_code = code_qv.value
        desc, soln = _findFault(ds_qv.value, fault_code)

        system.tag.writeBlocking([descPath, solnPath], [desc, soln])

        label = "F-{0} {1}".format(fault_code, desc)
        message = "Fault {0}: {1}\nSolution: {2}".format(fault_code, desc, soln)
        _ensureAlarmOnIsFaulted(isFaultedPath, label, message)

    elif (not newVal) and oldVal:
        system.tag.writeBlocking([descPath, solnPath], ["", ""])

except:
    pass

Update:
Based on feedback from the comments and some more thinking I figured out a solution that works much better.

Instead I am just searching through the dataset tag and assigning values to the Fault Desc and Fault Sol tags. Then I have a set alarm that uses the Fault Desc for the Label and isFaulted for the On Condition of the alarm. Thanks everyone.

Why not log the message contained in the Exception?

I removed the logs for readability for this post. There are not logs produced when trying to run it inside the UDT instance. Its like it never even attempts to do it. I even put a starting script log at the very beginning but nothing.

What are you actually attempting to solve here? Why do you need to add alarms on the fly? Why can’t you define a one time script that adds all required alarms to a list of target tags that you run any time you need to mass add/update a set?

Editing the tag configuration of the tag actively running the script seems like a bad™ design choice, at least to me. I feel like you would run into issues with the tag configuration application restarting the tag (and configured script) and causing issues.

Additionally, you should NOT be defining functions inside of a tag value changed script. Define these in the Gateway Scripting Project, and call them in the needed tag script.

You are also performing blocking work in a tag value change script, which risks blocking your ENTIRE tag system, especially since this is defined on a UDT.

If you still want to keep this approach, define it in a Gateway Tag Change Event. You can define folders to monitor using * as a wildcard. If you choose this approach, save yourself some trouble and define the actual script in the project library, and then call it from the Gateway Tag Change Event.

1 Like

The purpose is to create alarms based on the if a VFD is faulted and its last fault code to prevent needing to create individual alarms for the 100s to 1000s of VFD faults. With this is just sees that it is faulted gets the code references that dataset and tells the operator what the fault code and solution is based on that while also populating the alarm table with an alarm.

I never thought about doing it gateway side as a tagChange script. That would be a better approach as you pointed out. This was something I did very early on when I first started doing stuff with Ignition.

Script this action. You’ve already made a definition for the important/necessary faults. Your script is doing this to an extent.

Its less load to define it once on the UDT instead of overriding on EVERY udt instance on demand.

Additional gripe with your code:
_findFault is extremely inefficient and can be reduced to:

def _findFault(ds, code):
	""" """

	knownAlarms = ds.getColumnAsList(0)
	code = int(code)

	if code not in knownAlarms:
		return ("Unknown Fault (%d)" % code, "No documented solution.")

	codeNdx = knownAlarms.index(code)
	codeDesc = ds.getValueAt(codeNdx, 1)
	codeSoln = ds.getValueAt(codeNdx, 2)

	return (codeDesc, codeSoln)

There is no need to cast values coming out of a dataset to a certain type unless their type differs from what you are expecting. Columns are constrained to single type for the entire column.

I will revisit this based on your recommendations. I figured since I was only merging the changes that it did not have a huge impact. But they way you are describing things it seems like it does.

I missed this first time around, system.tag.editAlarmConfig doesn’t exist, where are you getting/defining this function?

Edit: Nevermind, found it in the deprecated section:

Since you are on version 8.1.44 I would recommend not using this function

We create a dataset tag for each drive type that contains the fault code and description.

Then in the PLC we have an Integer tag in the Drive UDT that we write the fault code to.

In the Ignition UDT, we just reference the dataset tag using lookup of the fault code in the dataset, and then have the alarm DisplayPath use the Fault description from the lookup.

2 Likes

Haha love it, I was lazy and had gpt remove the logs for readability and it did more than it was asked to do. That was supposed to be system.tag.configure.