From 913441972e4de39dbc3885bd21ab969c38ab3517 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 29 May 2026 15:43:24 -0400 Subject: [PATCH] feat(commons): native alarm source entities + ResolvedNativeAlarmSource --- .../Entities/Instances/Instance.cs | 2 + .../InstanceNativeAlarmSourceOverride.cs | 32 ++++++++++++++++ .../Entities/Templates/Template.cs | 5 +++ .../Templates/TemplateNativeAlarmSource.cs | 37 +++++++++++++++++++ .../Flattening/FlattenedConfiguration.cs | 21 +++++++++++ .../Entities/NativeAlarmSourceEntityTests.cs | 29 +++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/InstanceNativeAlarmSourceOverride.cs create mode 100644 src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/TemplateNativeAlarmSource.cs create mode 100644 tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/NativeAlarmSourceEntityTests.cs diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/Instance.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/Instance.cs index 0b5ad032..5f369856 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/Instance.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/Instance.cs @@ -22,6 +22,8 @@ public class Instance public ICollection AlarmOverrides { get; set; } = new List(); /// Data-connection bindings that map template tags to site data sources. public ICollection ConnectionBindings { get; set; } = new List(); + /// Per-instance overrides of template-defined native alarm source bindings. + public ICollection NativeAlarmSourceOverrides { get; set; } = new List(); /// /// Initializes a new instance with the required unique name. diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/InstanceNativeAlarmSourceOverride.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/InstanceNativeAlarmSourceOverride.cs new file mode 100644 index 00000000..9a2bdb4a --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/InstanceNativeAlarmSourceOverride.cs @@ -0,0 +1,32 @@ +namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances; + +/// +/// Per-instance override of a template-defined native alarm source. The source +/// reference is the field that varies per physical instance (Tank01 vs Tank02), +/// so per-instance override is the common case. Mirrors +/// InstanceAlarmOverride; a null override field leaves the inherited +/// value unchanged. +/// +public class InstanceNativeAlarmSourceOverride +{ + /// Primary key. + public int Id { get; set; } + /// Foreign key to the owning instance. + public int InstanceId { get; set; } + /// + /// Canonical name of the native alarm source being overridden — matches + /// ResolvedNativeAlarmSource.CanonicalName after flattening. + /// + public string SourceCanonicalName { get; set; } + /// Overrides the connection name when set; null = keep inherited. + public string? ConnectionNameOverride { get; set; } + /// Overrides the source reference when set; null = keep inherited. + public string? SourceReferenceOverride { get; set; } + /// Overrides the condition filter when set; null = keep inherited. + public string? ConditionFilterOverride { get; set; } + + /// Initializes a new override for the specified source binding. + /// Canonical name of the source to override. + public InstanceNativeAlarmSourceOverride(string sourceCanonicalName) => + SourceCanonicalName = sourceCanonicalName ?? throw new ArgumentNullException(nameof(sourceCanonicalName)); +} diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/Template.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/Template.cs index 7da38450..8684b626 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/Template.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/Template.cs @@ -38,6 +38,11 @@ public class Template /// Collection of compositions defined in this template. /// public ICollection Compositions { get; set; } = new List(); + /// + /// Collection of native alarm source bindings defined in this template + /// (read-only mirrors of OPC UA A&C / MxAccess Gateway alarm feeds). + /// + public ICollection NativeAlarmSources { get; set; } = new List(); /// /// True when this template was auto-derived to back a TemplateComposition diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/TemplateNativeAlarmSource.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/TemplateNativeAlarmSource.cs new file mode 100644 index 00000000..23f244d5 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/TemplateNativeAlarmSource.cs @@ -0,0 +1,37 @@ +namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; + +/// +/// A template-defined binding to a native alarm source: a data connection plus +/// a source reference (OPC UA SourceNode/notifier nodeId, or MxAccess +/// object/area). At deploy time the instance subscribes and mirrors all +/// conditions discovered under the source. Inheritance/lock semantics mirror +/// . +/// +public class TemplateNativeAlarmSource +{ + /// Database primary key. + public int Id { get; set; } + /// Foreign key to the owning . + public int TemplateId { get; set; } + /// Unique source binding name within the template. + public string Name { get; set; } + /// Optional human-readable description. + public string? Description { get; set; } + /// Name of the data connection that owns the alarm feed. + public string ConnectionName { get; set; } = string.Empty; + /// Source reference (OPC UA SourceNode/notifier nodeId, or MxAccess object/area). + public string SourceReference { get; set; } = string.Empty; + /// Optional condition filter; null = mirror all conditions under the source. + public string? ConditionFilter { get; set; } + /// When true, this binding cannot be overridden in derived templates. + public bool IsLocked { get; set; } + /// True when copied from a base template and not yet overridden on the derived template. + public bool IsInherited { get; set; } + /// Set on a base binding; when true derived templates may not override it. + public bool LockedInDerived { get; set; } + + /// Initializes a new binding with the specified name. + /// The unique binding name within the template. + public TemplateNativeAlarmSource(string name) => + Name = name ?? throw new ArgumentNullException(nameof(name)); +} diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs index 089a2469..f70f7c13 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs @@ -23,6 +23,8 @@ public sealed record FlattenedConfiguration public IReadOnlyList Alarms { get; init; } = []; /// Gets the resolved scripts. public IReadOnlyList Scripts { get; init; } = []; + /// Gets the resolved native alarm source bindings. + public IReadOnlyList NativeAlarmSources { get; init; } = []; /// Gets the UTC timestamp when this configuration was generated. public DateTimeOffset GeneratedAtUtc { get; init; } = DateTimeOffset.UtcNow; @@ -125,6 +127,25 @@ public sealed record ResolvedAlarm public string Source { get; init; } = "Template"; } +/// +/// A fully resolved native alarm source binding (data connection + source +/// reference). Conditions under the source are discovered at runtime; this +/// record only carries the binding, not individual alarms. +/// +public sealed record ResolvedNativeAlarmSource +{ + /// Gets the path-qualified canonical name. + public string CanonicalName { get; init; } = string.Empty; + /// Gets the data connection name that owns the alarm feed. + public string ConnectionName { get; init; } = string.Empty; + /// Gets the source reference (OPC UA SourceNode/notifier nodeId, or MxAccess object/area). + public string SourceReference { get; init; } = string.Empty; + /// Gets the optional condition filter; null = mirror all conditions under the source. + public string? ConditionFilter { get; init; } + /// Gets the source of this binding: "Template", "Inherited", "Composed", or "Override". + public string Source { get; init; } = "Template"; +} + /// /// A fully resolved script with code, trigger config, parameters, and return definition. /// diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/NativeAlarmSourceEntityTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/NativeAlarmSourceEntityTests.cs new file mode 100644 index 00000000..50374bc6 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/NativeAlarmSourceEntityTests.cs @@ -0,0 +1,29 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; +using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Entities; + +public class NativeAlarmSourceEntityTests +{ + [Fact] + public void TemplateNativeAlarmSource_RequiresName() + { + var s = new TemplateNativeAlarmSource("PressureMon") { ConnectionName = "Plant", SourceReference = "ns=2;s=P1" }; + Assert.Equal("PressureMon", s.Name); + Assert.False(s.IsLocked); + } + + [Fact] + public void FlattenedConfiguration_HasNativeAlarmSourcesDefaultEmpty() + { + var f = new FlattenedConfiguration(); + Assert.Empty(f.NativeAlarmSources); + } + + [Fact] + public void ResolvedNativeAlarmSource_DefaultsSourceTemplate() + { + var r = new ResolvedNativeAlarmSource { CanonicalName = "PressureMon", ConnectionName = "Plant", SourceReference = "ns=2;s=P1" }; + Assert.Equal("Template", r.Source); + } +}