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.
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
- In the Designer, expand the Tag Browser and navigate to the target tag or UDT instance.
- Right-click → Edit Tag (or double-click).
- Select the Events tab.
- 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:
- Open the Tag Browser and locate the target tag.
- Expand the tag and scroll to the meta.Quality property — this is the same
QualityCodesurfaced to the script, as documented in the Reading and Writing Tags course. - 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.
- Restore communications. The script should run again on the Good transition.
- Check the gateway log for the
log.infoentry — 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: returnat the very top — avoid any Python string formatting in the bad path. - Deferring heavy logic (database writes, alarm raises) to a
system.util.invokeLatercall so the subscription thread is not blocked. - Using
previousValue.qualitycomparisons 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.