Reading Tag Quality in Ignition 8.1 Change Event Scripts

Jason IP8 min read
Other ManufacturerSCADA ConfigurationTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview

In Inductive Automation Ignition 8.1, the currentValue object exposed inside a Tag Change event script is a QualifiedValue wrapper. It carries three fields engineers routinely need: value, quality, and timestamp. The quality field is not a string — it is a com.inductiveautomation.ignition.common.model.values.QualityCode object. Writing if currentValue.quality != 'Good': raises a type error and silently aborts the script, because the Jython runtime cannot coerce a QualityCode enum to a java.lang.String. This article shows the supported access patterns, the underlying API surface, and the version gating that controls IntelliSense for these members.

The behavior documented here is a direct consequence of how the Quality Codes and Overlays subsystem models tag health: each tag evaluation produces a discrete code from a fixed enumerator, and scripting layers receive the typed enum rather than a serialized label. Scripts written against the older string-comparison idiom (pre-8.1) must be updated when migrating.

Prerequisites

  • Ignition Gateway 8.1.18 or later. QualityCode IntelliSense (code completion for isGood(), isBad(), .good, .bad, etc.) ships with the nightly build line that became the 8.1.18 release. Earlier 8.1.x versions expose the methods at runtime, but the Designer script editor will not present them in the autocomplete dropdown.
  • A tag configured in the Tag Browser, with a UDT instance or atomic tag whose Value Changed event script is editable.
  • Designer Client permissions to edit tag event scripts (typically Authenticated User with Designer write access to the tag provider).
  • Jython 2.7 runtime knowledge — Ignition's script engine is Jython, not CPython. Imports use java.* namespaces.
If IntelliSense is not showing QualityCode members after pressing Ctrl+Space inside a Value Changed event, confirm the Designer is 8.1.18+. The script will still execute on the gateway if the gateway version supports the call, but the editor will not auto-complete it.

The QualityCode Type Explained

The quality field on currentValue is an instance of com.inductiveautomation.ignition.common.model.values.QualityCode, an enum-like class that encodes the OPC UA status code semantics. It is the same type returned by system.tag.readBlocking(...).quality and the TagQuality column in Power Tables bound to historical queries.

The class exposes the following members engineers will use in change-event scripts:

Member Return Type Purpose
isGood() boolean True when the code is Good (0x00000000). The most common guard clause.
isBad() boolean True for any non-Good code; useful as the inverse of isGood().
isNotGood() / isNotBad() boolean Explicit negations, prefer these in shared library code for clarity.
good (property) boolean Jython-NetBeans-compliant property accessor. Faster than isGood() in hot loops because it avoids a method-dispatch thunk.
bad (property) boolean Property form of isBad().
getCode() int Returns the underlying 32-bit OPC UA status integer (e.g. 0x80000000 for Bad).
getName() String Returns the enum constant name, e.g. "Bad_CommunicationError". This is not a friendly display label.
toString() String Localized, human-readable representation (e.g. "Bad(CommunicationError)"). Use this for logging.

The property form (currentValue.quality.good) is preferred over the method form (currentValue.quality.isGood()) when called inside tight event loops, since each Jython-to-Java method call incurs a runtime thunk. The performance difference is negligible per call, but accumulates on tag-change storms from 10k+ point scans.

Step-by-Step: Building a Quality-Gated Change Script

The following procedure replaces the broken if currentValue.quality != 'Good': pattern with a correct, version-safe implementation.

1. Open the Tag's Value Changed Event

  1. In the Designer, expand the Tag Browser and navigate to the target tag or UDT instance.
  2. Right-click → Edit Tag (or double-click).
  3. Select the Events tab.
  4. Expand Value Changed and click the Script Editor pen icon.

2. Use the Property Form (Recommended)

Write the guard clause using the boolean property accessor:

if not currentValue.quality.good:
    # Bail out before acting on a stale or invalid value
    system.tag.writeBlocking(['[default]Diagnostics/LastBadQuality'], currentValue.quality.toString())
    return

# Quality is Good — proceed with normal logic
newValue = currentValue.value
log.info("Tag updated to {}", newValue)

This pattern runs on every tag change, including quality changes themselves. Per the Tag Event Scripts training video, the Value Changed script fires on any subscription update — value, quality, or timestamp — so quality-only transitions will also reach this code path. The early return short-circuits stale-data work without raising an exception.

3. Use the Method Form (Compatible with Older 8.1.x)

If you must support gateways older than 8.1.18 (where the IntelliSense is incomplete but the runtime call still works), use the explicit method:

if not currentValue.quality.isGood():
    return

Both forms are functionally equivalent. Pick one style per project for grep-ability.

4. Distinguish Specific Bad Reasons

When you need to react differently to Bad_CommunicationError versus Bad_DeviceFailure, branch on the code integer or the name:

q = currentValue.quality
if q.bad:
    codeInt = q.getCode()
    if (codeInt & 0xFFFF0000) == 0x80040000:
        # Bad_DeviceFailure sub-range
        system.alarm.raiseSystemAlarm("Device failure on {}".format(tag.tagPath))
    elif (codeInt & 0xFFFF0000) == 0x80050000:
        # Bad_CommunicationError sub-range
        system.alarm.raiseSystemAlarm("Comm loss on {}".format(tag.tagPath))
    return

The bit-mask check (codeInt & 0xFFFF0000) extracts the OPC UA severity/subcode group, allowing you to bucket all Bad_ConfigurationError instances together without enumerating each constant.

Common QualityCode Values Reference

Hex Code Name Typical Cause
0x00000000 Good Last poll succeeded; data is current.
0x40000000 Uncertain Source returned a value but flagged uncertainty (e.g. out-of-range analog).
0x80000000 Bad Generic failure; not yet categorized.
0x80050000 Bad_CommunicationError Driver cannot reach the PLC or device.
0x80060000 Bad_DeviceFailure Device acknowledged but reported a hardware fault.
0x803A0000 Bad_ConfigurationError Tag path or address is misconfigured.
0x80400000 Bad_OutOfService Tag intentionally disabled via Out of Service toggle.
0x40800000 Uncertain_LastUsableValue Stale data retained after comms loss.

The full enumeration is published in the Quality Codes and Overlays manual page and mirrored in the SDK Javadoc for the QualityCode class.

Verification

To confirm the script behaves correctly across quality transitions:

  1. Open the Tag Browser and locate the target tag.
  2. Expand the tag and scroll to the meta.Quality property — this is the same QualityCode surfaced to the script, as documented in the Reading and Writing Tags course.
  3. Force a Bad quality by disabling the upstream device or simulating a comms loss. The script should run and the early-return path should fire.
  4. Restore communications. The script should run again on the Good transition.
  5. Check the gateway log for the log.info entry — its presence confirms execution reached the success branch.

For a non-destructive test, bind a numeric tag to a derived expression that returns Bad_ConfigurationError temporarily, and watch the diagnostic tag you wrote to in step 2 of the procedure above.

Troubleshooting Matrix

Symptom Likely Cause Fix
Script aborts with TypeError: cannot compare QualityCode to str String comparison quality != 'Good' left over from 7.x code Replace with not currentValue.quality.good or not currentValue.quality.isGood()
Ctrl+Space does not show isGood Designer version below 8.1.18 Update Designer; runtime call still works regardless
Script never fires on quality-only change Tag has no active subscription (e.g. expression tag with no client binding) Bind the tag to a component or use system.tag.readBlocking on a timer
quality.toString() returns localized text in a different language on a remote gateway Locale settings differ between gateways Use quality.getName() for language-stable code names
Quality changes too rapidly, log flooded Tag is in a flapping comms state with no debounce Add a hysteresis check: if previousValue.quality.good == currentValue.quality.good: return
Get attribute error on .good in older runtime Property accessor not exposed in pre-8.1 builds Use isGood() method form for cross-version safety

Performance Notes for High-Frequency Tags

On scan classes faster than 100 ms, or arrays of tags, the currentValue.quality dereference still costs one Jython-to-Java hop. If the change event performs only logging, consider:

  • Short-circuiting with if not currentValue.quality.good: return at the very top — avoid any Python string formatting in the bad path.
  • Deferring heavy logic (database writes, alarm raises) to a system.util.invokeLater call so the subscription thread is not blocked.
  • Using previousValue.quality comparisons to detect quality transitions rather than evaluating on every update.

Working Code: Quality-Transition Logger

A complete, field-tested pattern for logging only when quality flips from Good to anything else, or vice versa:

prev = previousValue.quality
curr = currentValue.quality

if prev.good and not curr.good:
    # Quality degraded
    log.warn("Tag {} went Bad: {}", tag.tagPath, curr.toString())
    system.tag.writeBlocking(['[default]Diagnostics/QualityFlips'],
                             system.tag.readBlocking(['[default]Diagnostics/QualityFlips'])[0].value + 1)
elif not prev.good and curr.good:
    # Quality recovered
    log.info("Tag {} recovered to Good", tag.tagPath)

# Normal value handling below
if not curr.good:
    return

newValue = currentValue.value
# ... downstream logic ...

Storing the flip counter as a tag makes the script's effect visible in the Tag Browser, which is invaluable during commissioning and SAT (Site Acceptance Testing).

FAQ

What is the data type of currentValue.quality in an Ignition tag change event?

It is a com.inductiveautomation.ignition.common.model.values.QualityCode object — an enum-like Java class — not a string. You must use its methods (isGood(), isBad()) or properties (.good, .bad) to inspect it.

Why does my script stop running when I add if currentValue.quality != 'Good':?

Because the comparison is between a QualityCode and the string 'Good'. Jython raises a TypeError on the comparison, which aborts the script. Use if not currentValue.quality.good: or if not currentValue.quality.isGood(): instead.

Why does IntelliSense not show isGood when I press Ctrl+Space inside a UDT Value Changed event?

Code completion for QualityCode members requires Designer version 8.1.18 or later. Earlier 8.1.x versions execute the calls correctly at runtime, but the script editor does not surface them in autocomplete. Update the Designer or rely on the property form .good.

Does the Value Changed event script run when only the quality changes and not the value?

Yes. The Value Changed subscription fires on any update — value, quality, or timestamp — so a quality-only transition will still execute the event script. Guard with if not currentValue.quality.good: return to skip logic on stale data.

How can I get the underlying integer OPC UA status code from a QualityCode?

Call currentValue.quality.getCode(). It returns a 32-bit integer matching the OPC UA status code namespace (e.g. 0x80050000 for Bad_CommunicationError). Use bit-mask comparisons with 0xFFFF0000 to bucket related errors.

Back to blog