UNS mirror Variable of an alarm-bearing raw tag stays BadWaitingForInitialData forever (raw side has only the condition) #896

Closed
opened 2026-11-12 23:34:14 -05:00 by dohertj2 · 1 comment
Owner

Found during the #615 G6 live gate (windev test Galaxy, 2026-11-12).

Symptom. An alarm-bearing raw tag that is referenced into equipment gets a UNS value Variable that never receives data. Example: the Galaxy tag TestAlarm002Condition (TagConfig with an alarm object) referenced into equipment testmachine-015. The UNS folder holds both of these:

  • [Variable] TestAlarm002Condition (ns=3;s=testarea/line-1/testmachine-015/TestAlarm002Condition). Reading it returns BadWaitingForInitialData permanently.
  • [Object] TestAlarm002Condition (ns=2;s=…/TestAlarm002Condition), the Part 9 condition from the notifier fan-out. This one works.

On the raw side the same tag materialises only the condition and no value variable. That is the documented contract: docs/ScriptedAlarms.md §Native driver alarms says ""alarm" present → the tag materialises as a Part 9 AlarmConditionState … instead of a value variable", and docs/AlarmTracking.md says "an alarm-bearing raw tag materializes only the condition, with no sibling value variable". The driver also excludes alarm-bearing tags from its value subscription ("subscribed to 5 refs" with 6 authored tags), so no value ever reaches the mirror.

Where. AddressSpaceComposer.BuildUnsReferenceVariables emits a UnsReferenceVariable for every reference without checking TagConfigIntent.Alarm. The deploy path reuses the composer, so both paths are affected.

Why not fixed alongside #895. AddressSpaceComposerDualNamespaceTests.ComposeFullFixture uses an alarm-bearing Modbus tag (Speed, LimitAlarm) as its main UNS-reference fixture. At least three tests assert that tag's UNS mirror (writable, historian intent, array shape). Skipping alarm-bearing references is a one-line change, but it needs those fixtures reworked and a decision on the intended shape: no mirror, or a mirror that is fed. A UNS client today sees a dead Variable next to the working condition.

Found during the #615 G6 live gate (windev test Galaxy, 2026-11-12). **Symptom.** An alarm-bearing raw tag that is referenced into equipment gets a UNS **value Variable** that never receives data. Example: the Galaxy tag `TestAlarm002Condition` (`TagConfig` with an `alarm` object) referenced into equipment `testmachine-015`. The UNS folder holds both of these: - `[Variable] TestAlarm002Condition (ns=3;s=testarea/line-1/testmachine-015/TestAlarm002Condition)`. Reading it returns `BadWaitingForInitialData` permanently. - `[Object] TestAlarm002Condition (ns=2;s=…/TestAlarm002Condition)`, the Part 9 condition from the notifier fan-out. This one works. On the raw side the same tag materialises **only** the condition and no value variable. That is the documented contract: `docs/ScriptedAlarms.md` §Native driver alarms says "`"alarm"` present → the tag materialises as a Part 9 `AlarmConditionState` … **instead of** a value variable", and `docs/AlarmTracking.md` says "an alarm-bearing raw tag materializes only the condition, with no sibling value variable". The driver also excludes alarm-bearing tags from its value subscription ("subscribed to 5 refs" with 6 authored tags), so no value ever reaches the mirror. **Where.** `AddressSpaceComposer.BuildUnsReferenceVariables` emits a `UnsReferenceVariable` for every reference without checking `TagConfigIntent.Alarm`. The deploy path reuses the composer, so both paths are affected. **Why not fixed alongside #895.** `AddressSpaceComposerDualNamespaceTests.ComposeFullFixture` uses an alarm-bearing Modbus tag (`Speed`, `LimitAlarm`) as its main UNS-reference fixture. At least three tests assert that tag's UNS mirror (writable, historian intent, array shape). Skipping alarm-bearing references is a one-line change, but it needs those fixtures reworked and a decision on the intended shape: no mirror, or a mirror that is fed. A UNS client today sees a dead Variable next to the working condition.
dohertj2 added the type:bugpriority:p3 labels 2026-11-12 23:34:14 -05:00
Author
Owner

Fixed in 09b373e4 on master.

Decision: the documented contract (condition only) is right. A reference to an alarm-bearing tag no longer gets a UNS value Variable. AddressSpaceComposer.BuildUnsReferenceVariables skips it (it checks the same TagConfigIntent.Alarm the raw pass reads). The equipment still gets the alarm: the reference keeps feeding RawTagPlan.ReferencingEquipmentPaths, so the single raw condition is still wired as an event notifier of the equipment folder.

Evidence

  • Raw side: AddressSpaceApplier.MaterialiseRawSubtree creates either a condition or a value variable for a tag, never both. An alarm tag has no raw value for a mirror to copy.
  • Runtime: DriverHostActor skips alarm tags (continue) before it registers the value fan-out and write routes, including the UNS NodeIds. The driver also leaves them out of its value subscription (G6: "subscribed to 5 refs" with 6 tags). Feeding the mirror would mean subscribing to a value the design says the tag does not have.
  • Docs: ScriptedAlarms.md says "instead of a value variable", AlarmTracking.md says "only the condition, with no sibling value variable", and the v3 two-subtree design says one condition at the raw tag, reaching equipment through notifiers.
  • History: the old assertions were incidental. f4f3e17e (v3 B4-WP1) put the alarm on the fixture's Speed tag to test raw alarm attachment. Later tests reused Speed for the mirror's writable, historian and array checks. No commit or doc chose a value mirror for alarm tags.
  • ScadaBridge: no code binds such a Variable. Its OtOpcUa alarm path is event subscriptions on equipment and raw folders (its v3 alarm B/C live gate). A node that never had a value cannot have been a working binding. G6 itself read the alarm's Boolean through a separate value tag (TestAlarm002), which is the supported way to do it. No ScadaBridge change is needed.

Tests

  • Composer fixture: the alarm now sits on its own HighTemp tag, and Speed is a plain value tag. The new test A_reference_to_an_alarm_bearing_tag_emits_no_uns_variable_but_still_reaches_the_equipment has two positive controls: the equipment path is still on the raw tag, and the value references still emit.
  • Real-SDK node manager: the new test Alarm_bearing_reference_surfaces_as_the_condition_only_against_real_SDK checks that there is no Variable at filling/line-1/station-1/HighTemp, that the Speed mirror is present, that there is 1 condition, and that the equipment folder is wired as its notifier.
  • Mutation check: with the skip removed, both new tests fail. With it restored, they pass.
  • OpcUaServer.Tests 1277 passed, 0 failed. Runtime.Tests 1037 passed, 0 failed. Release builds of OpcUaServer, Runtime and OpcUaServer.Tests: 0 warnings.
  • No live re-run against windev. The G6 ConfigDb and Host setup no longer exist, so the real-SDK test stands in for it.

Docs updated: Uns.md, ScriptedAlarms.md and the G6 evidence note.

Fixed in `09b373e4` on master. **Decision: the documented contract (condition only) is right.** A reference to an alarm-bearing tag no longer gets a UNS value Variable. `AddressSpaceComposer.BuildUnsReferenceVariables` skips it (it checks the same `TagConfigIntent.Alarm` the raw pass reads). The equipment still gets the alarm: the reference keeps feeding `RawTagPlan.ReferencingEquipmentPaths`, so the single raw condition is still wired as an event notifier of the equipment folder. **Evidence** - **Raw side:** `AddressSpaceApplier.MaterialiseRawSubtree` creates either a condition or a value variable for a tag, never both. An alarm tag has no raw value for a mirror to copy. - **Runtime:** `DriverHostActor` skips alarm tags (`continue`) before it registers the value fan-out and write routes, including the UNS NodeIds. The driver also leaves them out of its value subscription (G6: "subscribed to 5 refs" with 6 tags). Feeding the mirror would mean subscribing to a value the design says the tag does not have. - **Docs:** ScriptedAlarms.md says "instead of a value variable", AlarmTracking.md says "only the condition, with no sibling value variable", and the v3 two-subtree design says one condition at the raw tag, reaching equipment through notifiers. - **History:** the old assertions were incidental. `f4f3e17e` (v3 B4-WP1) put the alarm on the fixture's `Speed` tag to test raw alarm attachment. Later tests reused `Speed` for the mirror's writable, historian and array checks. No commit or doc chose a value mirror for alarm tags. - **ScadaBridge:** no code binds such a Variable. Its OtOpcUa alarm path is event subscriptions on equipment and raw folders (its v3 alarm B/C live gate). A node that never had a value cannot have been a working binding. G6 itself read the alarm's Boolean through a separate value tag (`TestAlarm002`), which is the supported way to do it. No ScadaBridge change is needed. **Tests** - Composer fixture: the alarm now sits on its own `HighTemp` tag, and `Speed` is a plain value tag. The new test `A_reference_to_an_alarm_bearing_tag_emits_no_uns_variable_but_still_reaches_the_equipment` has two positive controls: the equipment path is still on the raw tag, and the value references still emit. - Real-SDK node manager: the new test `Alarm_bearing_reference_surfaces_as_the_condition_only_against_real_SDK` checks that there is no Variable at `filling/line-1/station-1/HighTemp`, that the Speed mirror is present, that there is 1 condition, and that the equipment folder is wired as its notifier. - Mutation check: with the skip removed, both new tests fail. With it restored, they pass. - OpcUaServer.Tests 1277 passed, 0 failed. Runtime.Tests 1037 passed, 0 failed. Release builds of OpcUaServer, Runtime and OpcUaServer.Tests: 0 warnings. - No live re-run against windev. The G6 ConfigDb and Host setup no longer exist, so the real-SDK test stands in for it. Docs updated: Uns.md, ScriptedAlarms.md and the G6 evidence note.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#896