5.1 KiB
OtOpcUa v3 native-alarm B/C live-gate (Gitea #14) — PASS
Date: 2026-07-23 · Scope: items B (native-alarm dedup) + C (UNS-bound alarm routing) of #14,
against a UNS structure + scripted Part-9 alarm authored on the live otopcua-dev v3 cluster (see
2026-07-23-otopcua-v3-raw-path-live-gate.md for the connection + SiteAOnly substrate). Instrument:
ScadaBridge itself (its DCL A&C client is the thing under test).
Headline
Both B and C PASS, and item C's original premise turned out to be already-solved code. No ScadaBridge
code change is needed for B/C. The scope-doc worry — "a UNS-bound source won't StartsWith-match a
condition whose SourceName is the RawPath/ScriptedAlarmId" — is obsolete: ScadaBridge routes native
alarms by the subscribed node reference, not the event SourceName (deliberate fix, Gitea #17). The
only remaining #14 work is the item-A data re-author (greenfield, operational).
Substrate (on OtOpcUa, SITE-A cluster)
- UNS:
area-1/line-1/pump01→ signalSiteAOnly(equipment folder nodens=3;s=EQ-54f7711e160d, which hasEventNotifier=1; signalns=3;s=area-1/line-1/pump01/SiteAOnlymirrors the raw value). - Scripted Part-9 condition
ns=3;s=SA-siteahigh(aHasComponentchild of the equipment folder),AlarmConditionType, severity 700, predicate driven bySiteAOnly(set always-active for the gate). Server(i=2253)EventNotifier=1(the connection-wide aggregate).
What ScadaBridge did (bindings on template 2148, instance 98, connection OtOpcUa-v3-raw)
Two TemplateNativeAlarmSource bindings, deliberately overlapping to stress dedup:
ServerAlarms→--source-ref i=2253(Server aggregate)UnsEquipAlarms→--source-ref nsu=https://zb.com/otopcua/uns;s=EQ-54f7711e160d(UNS equipment node)
Findings / checks
| # | Check | Result |
|---|---|---|
| C1 | A UNS-node-scoped binding receives + routes the native alarm | PASS — UnsEquipAlarms mirrored SA-siteahigh.SiteA High active=True, sev 700, kind=NativeOpcUa |
| C2 | The condition's SourceName = ScriptedAlarmId (SA-siteahigh), NOT RawPath |
Confirmed (captured live) — and it does not break routing (routing keys off the subscribed node, Gitea #17), only feeds the alarm's identity (alarmName = SA-siteahigh.SiteA High) |
| C3 | Item C's original "SourceName mismatch drops UNS-bound transitions" premise | Obsolete — OpcUaAlarmMapper.BuildIdentity stamps SourceObjectReference = the subscription ref; DataConnectionActor routes on that, not SourceName |
| B1 | Fanned condition (Server aggregate + equipment-folder notifier) mirrored once, not duplicated | PASS — active condition appears exactly once (on the more-specific equipment feed); the Server binding stays an inactive placeholder. Stable across repeated snapshots |
| B2 | Mechanism | ScadaBridge opens one OPC UA Subscription per connection with one MonitoredItem per source-ref; ConditionRefresh delivers each retained condition once per subscription, to the most-specific monitored notifier (the equipment folder) — so overlapping bindings do not double-mirror the fan-out |
| B3 | Residual double-mirror risk | Latent, not triggered here: it needs two bindings whose SourceReference strings are literal prefixes of one another AND a delivery matching both (DataConnectionActor has no cross-binding, per-ConditionId dedup — InstanceActor._latestAlarmEvents is last-writer-wins by alarmName). Disjoint node refs (as here) don't collide |
Captured real payload (what §5 phase 3 wanted)
kind=NativeOpcUa, alarmName='SA-siteahigh.SiteA High' (SourceName=SA-siteahigh = ScriptedAlarmId,
. + ConditionName=SiteA High), alarmTypeName='AlarmConditionType', severity=700,
condition.active=True. A&C state is delivered via events/ConditionRefresh (ScadaBridge triggers a
refresh on every subscribe), not a plain attribute read.
Bottom line for #14
- A — raw/UNS binding re-author: validated end-to-end (raw-path gate proved
nsu=bind→read; this gate proved UNS bind→alarm). Remaining work is data re-authoring (greenfield), no code. - B — dedup: PASS, no double-mirror across the v3 fan-out (one subscription +
ConditionRefresh). - C — UNS-bound routing: PASS; the original concern was already solved by Gitea #17.
- D/E/F — shipped in phase 2 (
2026-07-23-otopcua-v3-nsu-hardening-and-browse-ux.md).
#14 needs no further ScadaBridge code for B/C. The only shipped code from this whole effort was the
phase-2 nsu= hardening + the RealOpcUaClient host-rewrite fix (surfaced by the raw gate). #14 can close
once item A's re-author is done in the target environment(s).
Rig artifacts (removable on next docker/deploy.sh)
OtOpcUa ConfigDb: UnsArea SITEA-area1 / UnsLine SITEA-line1 / Equipment EQ-54f7711e160d /
UnsTagReference UTR-siteapump01-siteaonly / Script SC-siteahigh / ScriptedAlarm SA-siteahigh.
ScadaBridge: template 2148 native-alarm-sources ServerAlarms + UnsEquipAlarms; instance 98; connection 3044.