feat(commons): native alarm source entities + ResolvedNativeAlarmSource

This commit is contained in:
Joseph Doherty
2026-05-29 15:43:24 -04:00
parent ea14ace150
commit 913441972e
6 changed files with 126 additions and 0 deletions
@@ -22,6 +22,8 @@ public class Instance
public ICollection<InstanceAlarmOverride> AlarmOverrides { get; set; } = new List<InstanceAlarmOverride>();
/// <summary>Data-connection bindings that map template tags to site data sources.</summary>
public ICollection<InstanceConnectionBinding> ConnectionBindings { get; set; } = new List<InstanceConnectionBinding>();
/// <summary>Per-instance overrides of template-defined native alarm source bindings.</summary>
public ICollection<InstanceNativeAlarmSourceOverride> NativeAlarmSourceOverrides { get; set; } = new List<InstanceNativeAlarmSourceOverride>();
/// <summary>
/// Initializes a new instance with the required unique name.
@@ -0,0 +1,32 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
/// <summary>
/// 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
/// <c>InstanceAlarmOverride</c>; a null override field leaves the inherited
/// value unchanged.
/// </summary>
public class InstanceNativeAlarmSourceOverride
{
/// <summary>Primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the owning instance.</summary>
public int InstanceId { get; set; }
/// <summary>
/// Canonical name of the native alarm source being overridden — matches
/// <c>ResolvedNativeAlarmSource.CanonicalName</c> after flattening.
/// </summary>
public string SourceCanonicalName { get; set; }
/// <summary>Overrides the connection name when set; null = keep inherited.</summary>
public string? ConnectionNameOverride { get; set; }
/// <summary>Overrides the source reference when set; null = keep inherited.</summary>
public string? SourceReferenceOverride { get; set; }
/// <summary>Overrides the condition filter when set; null = keep inherited.</summary>
public string? ConditionFilterOverride { get; set; }
/// <summary>Initializes a new override for the specified source binding.</summary>
/// <param name="sourceCanonicalName">Canonical name of the source to override.</param>
public InstanceNativeAlarmSourceOverride(string sourceCanonicalName) =>
SourceCanonicalName = sourceCanonicalName ?? throw new ArgumentNullException(nameof(sourceCanonicalName));
}
@@ -38,6 +38,11 @@ public class Template
/// Collection of compositions defined in this template.
/// </summary>
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
/// <summary>
/// Collection of native alarm source bindings defined in this template
/// (read-only mirrors of OPC UA A&amp;C / MxAccess Gateway alarm feeds).
/// </summary>
public ICollection<TemplateNativeAlarmSource> NativeAlarmSources { get; set; } = new List<TemplateNativeAlarmSource>();
/// <summary>
/// True when this template was auto-derived to back a TemplateComposition
@@ -0,0 +1,37 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
/// <summary>
/// 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
/// <see cref="TemplateAlarm"/>.
/// </summary>
public class TemplateNativeAlarmSource
{
/// <summary>Database primary key.</summary>
public int Id { get; set; }
/// <summary>Foreign key to the owning <see cref="Template"/>.</summary>
public int TemplateId { get; set; }
/// <summary>Unique source binding name within the template.</summary>
public string Name { get; set; }
/// <summary>Optional human-readable description.</summary>
public string? Description { get; set; }
/// <summary>Name of the data connection that owns the alarm feed.</summary>
public string ConnectionName { get; set; } = string.Empty;
/// <summary>Source reference (OPC UA SourceNode/notifier nodeId, or MxAccess object/area).</summary>
public string SourceReference { get; set; } = string.Empty;
/// <summary>Optional condition filter; null = mirror all conditions under the source.</summary>
public string? ConditionFilter { get; set; }
/// <summary>When true, this binding cannot be overridden in derived templates.</summary>
public bool IsLocked { get; set; }
/// <summary>True when copied from a base template and not yet overridden on the derived template.</summary>
public bool IsInherited { get; set; }
/// <summary>Set on a base binding; when true derived templates may not override it.</summary>
public bool LockedInDerived { get; set; }
/// <summary>Initializes a new binding with the specified name.</summary>
/// <param name="name">The unique binding name within the template.</param>
public TemplateNativeAlarmSource(string name) =>
Name = name ?? throw new ArgumentNullException(nameof(name));
}
@@ -23,6 +23,8 @@ public sealed record FlattenedConfiguration
public IReadOnlyList<ResolvedAlarm> Alarms { get; init; } = [];
/// <summary>Gets the resolved scripts.</summary>
public IReadOnlyList<ResolvedScript> Scripts { get; init; } = [];
/// <summary>Gets the resolved native alarm source bindings.</summary>
public IReadOnlyList<ResolvedNativeAlarmSource> NativeAlarmSources { get; init; } = [];
/// <summary>Gets the UTC timestamp when this configuration was generated.</summary>
public DateTimeOffset GeneratedAtUtc { get; init; } = DateTimeOffset.UtcNow;
@@ -125,6 +127,25 @@ public sealed record ResolvedAlarm
public string Source { get; init; } = "Template";
}
/// <summary>
/// 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.
/// </summary>
public sealed record ResolvedNativeAlarmSource
{
/// <summary>Gets the path-qualified canonical name.</summary>
public string CanonicalName { get; init; } = string.Empty;
/// <summary>Gets the data connection name that owns the alarm feed.</summary>
public string ConnectionName { get; init; } = string.Empty;
/// <summary>Gets the source reference (OPC UA SourceNode/notifier nodeId, or MxAccess object/area).</summary>
public string SourceReference { get; init; } = string.Empty;
/// <summary>Gets the optional condition filter; null = mirror all conditions under the source.</summary>
public string? ConditionFilter { get; init; }
/// <summary>Gets the source of this binding: "Template", "Inherited", "Composed", or "Override".</summary>
public string Source { get; init; } = "Template";
}
/// <summary>
/// A fully resolved script with code, trigger config, parameters, and return definition.
/// </summary>
@@ -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);
}
}