Resolving Missing VariableChange Events in FTOptix Digital I/O

Summary

Implementing a VariableChange handler on FTOptix digital I/O requires attaching to the correct node type and keeping the subscription alive. The posted code attaches the handler to a Tag object that is never updated because the underlying IUAVariable does not raise VariableChange events unless its DataChange flag is enabled and the node is a variable, not a tag wrapper.


Root Cause

  • The handler is registered on a Tag wrapper (tIO.VariableChange += OnChange;).
    Tag only forwards change notifications when the parent IUAVariable signals a data change. In the current loop the Tag instance is discarded after the iteration, so the runtime does not retain a reference to it.
  • VariableChangeEventArgs is never populated because the call OnChange(tIO, null); passes null. The runtime expects an actual event argument generated by the engine.
  • The module alias (aModule) points to a folder, not directly to the variables that generate change events. Subscribing to the folder’s children without enabling DataChange on each variable results in no events.

Why This Happens in Real Systems

  • Transient wrapper objects: When you cast a node to a wrapper class (e.g., Tag) and do not store the wrapper, the GC can collect it, breaking the event pipeline.
  • Missing DataChange configuration: In FTOptix, a variable must have the DataChange attribute set to true (or be a Variable node with EventNotifier set) for change notifications to be emitted.
  • Incorrect node path: Using an alias that resolves to a folder rather than the actual variable leads to a silent subscription failure.

Real-World Impact

  • No runtime feedback: Operators see stale I/O states, potentially causing safety incidents.
  • Debugging overhead: Engineers waste time chasing “missing events” that are silently dropped.
  • Resource leakage: Re‑creating handlers on every start without proper disposal can leak memory and degrade performance.

Example or Code (if necessary and relevant)

public override void Start()
{
    Log.Info("clsLiveIO.cs Start()...");

    var moduleNode = Owner.GetAlias(mcstrAliasModule) as IUANode;
    if (moduleNode == null)
        throw new Exception($"Alias '{mcstrAliasModule}' not found");

    // Find all variable children under the module
    var variables = moduleNode.GetChildren()
                              .OfType()
                              .Where(v => v.DataChange)   // ensure change notifications are enabled
                              .ToArray();

    foreach (var variable in variables)
    {
        // Keep a strong reference to the wrapper so it is not GC'd
        var tag = variable as Tag;
        if (tag == null)
        {
            Log.Warning($"Node {variable.BrowseName} is not a Tag, skipping");
            continue;
        }

        tag.VariableChange += OnChange;
        Log.Info($"Subscribed VariableChange for {variable.BrowseName}");
    }

    Log.Info("clsLiveIO.cs Start() completed...");
}

How Senior Engineers Fix It

  • Store the wrapper (Tag) in a class‑level collection to prevent garbage collection.
  • Validate that each IUAVariable has DataChange = true in the information model; enable it programmatically if needed: variable.DataChange = true;.
  • Subscribe to the exact variable nodes rather than a folder alias; use GetChildren() with a filter or explicit paths ("aModule/Pt00/Data").
  • Avoid calling the handler manually with null args; let the engine invoke it, or create a proper VariableChangeEventArgs when testing.
  • Unsubscribe in Stop() to avoid dangling handlers:
    foreach (var tag in _registeredTags) tag.VariableChange -= OnChange;
    _registeredTags.Clear();

Why Juniors Miss It

  • Assume wrapper objects are permanent: Newcomers often think casting to Tag creates a permanent subscription, overlooking the need for a strong reference.
  • Ignore DataChange flag: The default model may have it disabled, and juniors tend to focus on code rather than model configuration.
  • Use aliases liberally: They treat an alias as a direct pointer to a variable, not realizing it can resolve to a folder hierarchy.
  • Test with manual calls: Passing null to the handler masks the fact that the engine never fires the event, giving a false sense of correctness.

Leave a Comment