docs(plans): OtOpcUa v3 native-alarm B/C live-gate PASS (#14) — no code needed; item C premise obsolete (Gitea #17)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# 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` → signal `SiteAOnly` (equipment folder node `ns=3;s=EQ-54f7711e160d`,
|
||||
which has **`EventNotifier=1`**; signal `ns=3;s=area-1/line-1/pump01/SiteAOnly` mirrors the raw value).
|
||||
- Scripted Part-9 condition `ns=3;s=SA-siteahigh` (a `HasComponent` child of the equipment folder),
|
||||
`AlarmConditionType`, severity 700, predicate driven by `SiteAOnly` (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.
|
||||
Reference in New Issue
Block a user