chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>Physical OPC UA server node within a <see cref="ServerCluster"/>.</summary>
|
||||
public sealed class ClusterNode
|
||||
{
|
||||
/// <summary>Stable per-machine logical ID, e.g. "LINE3-OPCUA-A".</summary>
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required RedundancyRole RedundancyRole { get; set; }
|
||||
|
||||
/// <summary>Machine hostname / IP.</summary>
|
||||
public required string Host { get; set; }
|
||||
|
||||
public int OpcUaPort { get; set; } = 4840;
|
||||
|
||||
public int DashboardPort { get; set; } = 8081;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA <c>ApplicationUri</c> — MUST be unique per node per OPC UA spec. Clients pin trust here.
|
||||
/// Fleet-wide unique index enforces no two nodes share a value (decision #86).
|
||||
/// Stored explicitly, NOT derived from <see cref="Host"/> at runtime — silent rewrite on
|
||||
/// hostname change would break all client trust.
|
||||
/// </summary>
|
||||
public required string ApplicationUri { get; set; }
|
||||
|
||||
/// <summary>Primary = 200, Secondary = 150 by default.</summary>
|
||||
public byte ServiceLevelBase { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Per-node override JSON keyed by DriverInstanceId, merged onto cluster-level DriverConfig
|
||||
/// at apply time. Minimal by intent (decision #81). Nullable when no overrides exist.
|
||||
/// </summary>
|
||||
public string? DriverConfigOverridesJson { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public DateTime? LastSeenAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
// Navigation
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
public ICollection<ClusterNodeCredential> Credentials { get; set; } = [];
|
||||
public ClusterNodeGenerationState? GenerationState { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates a <see cref="ClusterNode"/> to the central config DB.
|
||||
/// Per decision #83 — credentials bind to NodeId, not ClusterId.
|
||||
/// </summary>
|
||||
public sealed class ClusterNodeCredential
|
||||
{
|
||||
public Guid CredentialId { get; set; }
|
||||
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
public required CredentialKind Kind { get; set; }
|
||||
|
||||
/// <summary>Login name / cert thumbprint / SID / gMSA name.</summary>
|
||||
public required string Value { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public DateTime? RotatedAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
public ClusterNode? Node { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks which generation each node has applied. Per-node (not per-cluster) — both nodes of a
|
||||
/// 2-node cluster track independently per decision #84.
|
||||
/// </summary>
|
||||
public sealed class ClusterNodeGenerationState
|
||||
{
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
public long? CurrentGenerationId { get; set; }
|
||||
|
||||
public DateTime? LastAppliedAt { get; set; }
|
||||
|
||||
public NodeApplyStatus? LastAppliedStatus { get; set; }
|
||||
|
||||
public string? LastAppliedError { get; set; }
|
||||
|
||||
/// <summary>Updated on every poll for liveness detection.</summary>
|
||||
public DateTime? LastSeenAt { get; set; }
|
||||
|
||||
public ClusterNode? Node { get; set; }
|
||||
public ConfigGeneration? CurrentGeneration { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Append-only audit log for every config write + authorization-check event. Grants revoked for
|
||||
/// UPDATE / DELETE on all principals (enforced by the authorization migration in B.3).
|
||||
/// </summary>
|
||||
public sealed class ConfigAuditLog
|
||||
{
|
||||
public long AuditId { get; set; }
|
||||
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string Principal { get; set; }
|
||||
|
||||
/// <summary>DraftCreated | DraftEdited | Published | RolledBack | NodeApplied | CredentialAdded | CredentialDisabled | ClusterCreated | NodeAdded | ExternalIdReleased | CrossClusterNamespaceAttempt | OpcUaAccessDenied | …</summary>
|
||||
public required string EventType { get; set; }
|
||||
|
||||
public string? ClusterId { get; set; }
|
||||
|
||||
public string? NodeId { get; set; }
|
||||
|
||||
public long? GenerationId { get; set; }
|
||||
|
||||
public string? DetailsJson { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Atomic, immutable snapshot of one cluster's configuration.
|
||||
/// Per decision #82 — cluster-scoped, not fleet-scoped.
|
||||
/// </summary>
|
||||
public sealed class ConfigGeneration
|
||||
{
|
||||
/// <summary>Monotonically increasing ID, generated by <c>IDENTITY(1, 1)</c>.</summary>
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required GenerationStatus Status { get; set; }
|
||||
|
||||
public long? ParentGenerationId { get; set; }
|
||||
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
|
||||
public string? PublishedBy { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
public ConfigGeneration? Parent { get; set; }
|
||||
}
|
||||
23
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs
Normal file
23
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>Per-device row for multi-device drivers (Modbus, AB CIP). Optional for single-device drivers.</summary>
|
||||
public sealed class Device
|
||||
{
|
||||
public Guid DeviceRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string DeviceId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="DriverInstance.DriverInstanceId"/>.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Schemaless per-driver-type device config (host, port, unit ID, slot, etc.).</summary>
|
||||
public required string DeviceConfig { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Per-host connectivity snapshot the Server publishes for each driver's
|
||||
/// <c>IHostConnectivityProbe.GetHostStatuses</c> entry. One row per
|
||||
/// (<see cref="NodeId"/>, <see cref="DriverInstanceId"/>, <see cref="HostName"/>) triple —
|
||||
/// a redundant 2-node cluster with one Galaxy driver reporting 3 platforms produces 6
|
||||
/// rows, not 3, because each server node owns its own runtime view.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Closes the data-layer piece of LMX follow-up #7 (per-AppEngine Admin dashboard
|
||||
/// drill-down). The publisher hosted service on the Server side subscribes to every
|
||||
/// registered driver's <c>OnHostStatusChanged</c> and upserts rows on transitions +
|
||||
/// periodic liveness heartbeats. <see cref="LastSeenUtc"/> advances on every
|
||||
/// heartbeat so the Admin UI can flag stale rows from a crashed Server.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// No foreign-key to <see cref="ClusterNode"/> — a Server may start reporting host
|
||||
/// status before its ClusterNode row exists (e.g. first-boot bootstrap), and we'd
|
||||
/// rather keep the status row than drop it. The Admin-side service left-joins on
|
||||
/// NodeId when presenting rows.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class DriverHostStatus
|
||||
{
|
||||
/// <summary>Server node that's running the driver.</summary>
|
||||
public required string NodeId { get; set; }
|
||||
|
||||
/// <summary>Driver instance's stable id (matches <c>IDriver.DriverInstanceId</c>).</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Driver-side host identifier — Galaxy Platform / AppEngine name, Modbus
|
||||
/// <c>host:port</c>, whatever the probe returns. Opaque to the Admin UI except as
|
||||
/// a display string.
|
||||
/// </summary>
|
||||
public required string HostName { get; set; }
|
||||
|
||||
public DriverHostState State { get; set; } = DriverHostState.Unknown;
|
||||
|
||||
/// <summary>Timestamp of the last state transition (not of the most recent heartbeat).</summary>
|
||||
public DateTime StateChangedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Advances on every publisher heartbeat — the Admin UI uses
|
||||
/// <c>now - LastSeenUtc > threshold</c> to flag rows whose owning Server has
|
||||
/// stopped reporting (crashed, network-partitioned, etc.), independent of
|
||||
/// <see cref="State"/>.
|
||||
/// </summary>
|
||||
public DateTime LastSeenUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional human-readable detail populated when <see cref="State"/> is
|
||||
/// <see cref="DriverHostState.Faulted"/> — e.g. the exception message from the
|
||||
/// driver's probe. Null for Running / Stopped / Unknown transitions.
|
||||
/// </summary>
|
||||
public string? Detail { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>One driver instance in a cluster's generation. JSON config is schemaless per-driver-type.</summary>
|
||||
public sealed class DriverInstance
|
||||
{
|
||||
public Guid DriverInstanceRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logical FK to <see cref="Namespace.NamespaceId"/>. Same-cluster binding enforced by
|
||||
/// <c>sp_ValidateDraft</c> per decision #122: Namespace.ClusterId must equal DriverInstance.ClusterId.
|
||||
/// </summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Galaxy | ModbusTcp | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient</summary>
|
||||
public required string DriverType { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Schemaless per-driver-type JSON config. Validated against registered JSON schema at draft-publish time (decision #91).</summary>
|
||||
public required string DriverConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional per-instance overrides for the Phase 6.1 shared Polly resilience pipeline.
|
||||
/// Null = use the driver's tier defaults (decision #143). When populated, expected shape:
|
||||
/// <code>
|
||||
/// {
|
||||
/// "bulkheadMaxConcurrent": 16,
|
||||
/// "bulkheadMaxQueue": 64,
|
||||
/// "capabilityPolicies": {
|
||||
/// "Read": { "timeoutSeconds": 5, "retryCount": 5, "breakerFailureThreshold": 3 },
|
||||
/// "Write": { "timeoutSeconds": 5, "retryCount": 0, "breakerFailureThreshold": 5 }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// Parsed at startup by <c>DriverResilienceOptionsParser</c>; every key is optional +
|
||||
/// unrecognised keys are ignored so future shapes land without a migration.
|
||||
/// </summary>
|
||||
public string? ResilienceConfig { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Runtime resilience counters the CapabilityInvoker + MemoryTracking + MemoryRecycle
|
||||
/// surfaces for each <c>(DriverInstanceId, HostName)</c> pair. Separate from
|
||||
/// <see cref="DriverHostStatus"/> (which owns per-host <i>connectivity</i> state) so a
|
||||
/// host that's Running but has tripped its breaker or is approaching its memory ceiling
|
||||
/// shows up distinctly on Admin <c>/hosts</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/implementation/phase-6-1-resilience-and-observability.md</c> §Stream E.1.
|
||||
/// The Admin UI left-joins this table on DriverHostStatus for display; rows are written
|
||||
/// by the runtime via a HostedService that samples the tracker at a configurable
|
||||
/// interval (default 5 s) — writes are non-critical, a missed sample is tolerated.
|
||||
/// </remarks>
|
||||
public sealed class DriverInstanceResilienceStatus
|
||||
{
|
||||
public required string DriverInstanceId { get; set; }
|
||||
public required string HostName { get; set; }
|
||||
|
||||
/// <summary>Most recent time the circuit breaker for this (instance, host) opened; null if never.</summary>
|
||||
public DateTime? LastCircuitBreakerOpenUtc { get; set; }
|
||||
|
||||
/// <summary>Rolling count of consecutive Polly pipeline failures for this (instance, host).</summary>
|
||||
public int ConsecutiveFailures { get; set; }
|
||||
|
||||
/// <summary>Current Polly bulkhead depth (in-flight calls) for this (instance, host).</summary>
|
||||
public int CurrentBulkheadDepth { get; set; }
|
||||
|
||||
/// <summary>Most recent process recycle time (Tier C only; null for in-process tiers).</summary>
|
||||
public DateTime? LastRecycleUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Post-init memory baseline captured by <c>MemoryTracking</c> (median of first
|
||||
/// BaselineWindow samples). Zero while still warming up.
|
||||
/// </summary>
|
||||
public long BaselineFootprintBytes { get; set; }
|
||||
|
||||
/// <summary>Most recent footprint sample the tracker saw (steady-state read).</summary>
|
||||
public long CurrentFootprintBytes { get; set; }
|
||||
|
||||
/// <summary>Row last-write timestamp — advances on every sampling tick.</summary>
|
||||
public DateTime LastSampledUtc { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// UNS level-5 entity. Only for drivers in Equipment-kind namespaces.
|
||||
/// Per decisions #109 (first-class), #116 (5-identifier model), #125 (system-generated EquipmentId),
|
||||
/// #138–139 (OPC 40010 Identification fields as first-class columns).
|
||||
/// </summary>
|
||||
public sealed class Equipment
|
||||
{
|
||||
public Guid EquipmentRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// System-generated stable internal logical ID. Format: <c>'EQ-' + first 12 hex chars of EquipmentUuid</c>.
|
||||
/// NEVER operator-supplied, NEVER in CSV imports, NEVER editable in Admin UI (decision #125).
|
||||
/// </summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>UUIDv4, IMMUTABLE across all generations of the same EquipmentId. Downstream-consumer join key.</summary>
|
||||
public Guid EquipmentUuid { get; set; }
|
||||
|
||||
/// <summary>Logical FK to the driver providing data for this equipment.</summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>Optional logical FK to a multi-device driver's device.</summary>
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="UnsLine.UnsLineId"/>.</summary>
|
||||
public required string UnsLineId { get; set; }
|
||||
|
||||
/// <summary>UNS level 5 segment, matches <c>^[a-z0-9-]{1,32}$</c>.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
// Operator-facing / external-system identifiers (decision #116)
|
||||
|
||||
/// <summary>Operator colloquial id (e.g. "machine_001"). Unique within cluster. Required.</summary>
|
||||
public required string MachineCode { get; set; }
|
||||
|
||||
/// <summary>ERP equipment id. Unique fleet-wide via <see cref="ExternalIdReservation"/>. Primary browse identifier in Admin UI.</summary>
|
||||
public string? ZTag { get; set; }
|
||||
|
||||
/// <summary>SAP PM equipment id. Unique fleet-wide via <see cref="ExternalIdReservation"/>.</summary>
|
||||
public string? SAPID { get; set; }
|
||||
|
||||
// OPC UA Companion Spec OPC 40010 Machinery Identification fields (decision #139).
|
||||
// All nullable so equipment can be added before identity is fully captured.
|
||||
public string? Manufacturer { get; set; }
|
||||
public string? Model { get; set; }
|
||||
public string? SerialNumber { get; set; }
|
||||
public string? HardwareRevision { get; set; }
|
||||
public string? SoftwareRevision { get; set; }
|
||||
public short? YearOfConstruction { get; set; }
|
||||
public string? AssetLocation { get; set; }
|
||||
public string? ManufacturerUri { get; set; }
|
||||
public string? DeviceManualUri { get; set; }
|
||||
|
||||
/// <summary>Nullable hook for future schemas-repo template ID (decision #112).</summary>
|
||||
public string? EquipmentClassRef { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Staged equipment-import batch per Phase 6.4 Stream B.2. Rows land in the child
|
||||
/// <see cref="EquipmentImportRow"/> table under a batch header; operator reviews + either
|
||||
/// drops (via <c>DropImportBatch</c>) or finalises (via <c>FinaliseImportBatch</c>) in one
|
||||
/// bounded transaction. The live <c>Equipment</c> table never sees partial state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>User-scoped visibility: the preview modal only shows batches where
|
||||
/// <see cref="CreatedBy"/> equals the current operator. Prevents accidental
|
||||
/// cross-operator finalise during concurrent imports. An admin finalise / drop surface
|
||||
/// can override this — tracked alongside the UI follow-up.</para>
|
||||
///
|
||||
/// <para><see cref="FinalisedAtUtc"/> stamps the moment the batch promoted from staging
|
||||
/// into <c>Equipment</c>. Null = still in staging; non-null = archived / finalised.</para>
|
||||
/// </remarks>
|
||||
public sealed class EquipmentImportBatch
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public required string ClusterId { get; set; }
|
||||
public required string CreatedBy { get; set; }
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
public int RowsStaged { get; set; }
|
||||
public int RowsAccepted { get; set; }
|
||||
public int RowsRejected { get; set; }
|
||||
public DateTime? FinalisedAtUtc { get; set; }
|
||||
|
||||
public ICollection<EquipmentImportRow> Rows { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One staged row under an <see cref="EquipmentImportBatch"/>. Mirrors the decision #117
|
||||
/// + decision #139 columns from the CSV importer's output + an
|
||||
/// <see cref="IsAccepted"/> flag + a <see cref="RejectReason"/> string the preview modal
|
||||
/// renders.
|
||||
/// </summary>
|
||||
public sealed class EquipmentImportRow
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid BatchId { get; set; }
|
||||
public int LineNumberInFile { get; set; }
|
||||
public bool IsAccepted { get; set; }
|
||||
public string? RejectReason { get; set; }
|
||||
|
||||
// Required (decision #117)
|
||||
public required string ZTag { get; set; }
|
||||
public required string MachineCode { get; set; }
|
||||
public required string SAPID { get; set; }
|
||||
public required string EquipmentId { get; set; }
|
||||
public required string EquipmentUuid { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string UnsAreaName { get; set; }
|
||||
public required string UnsLineName { get; set; }
|
||||
|
||||
// Optional (decision #139 — OPC 40010 Identification)
|
||||
public string? Manufacturer { get; set; }
|
||||
public string? Model { get; set; }
|
||||
public string? SerialNumber { get; set; }
|
||||
public string? HardwareRevision { get; set; }
|
||||
public string? SoftwareRevision { get; set; }
|
||||
public string? YearOfConstruction { get; set; }
|
||||
public string? AssetLocation { get; set; }
|
||||
public string? ManufacturerUri { get; set; }
|
||||
public string? DeviceManualUri { get; set; }
|
||||
|
||||
public EquipmentImportBatch? Batch { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Fleet-wide rollback-safe reservation of ZTag and SAPID. Per decision #124 — NOT generation-versioned.
|
||||
/// Exists outside generation flow specifically because old generations and disabled equipment can
|
||||
/// still hold the same external IDs; per-generation uniqueness indexes fail under rollback/re-enable.
|
||||
/// </summary>
|
||||
public sealed class ExternalIdReservation
|
||||
{
|
||||
public Guid ReservationId { get; set; }
|
||||
|
||||
public required ReservationKind Kind { get; set; }
|
||||
|
||||
public required string Value { get; set; }
|
||||
|
||||
/// <summary>The equipment that owns this reservation. Stays bound even when equipment is disabled.</summary>
|
||||
public Guid EquipmentUuid { get; set; }
|
||||
|
||||
/// <summary>First cluster to publish this reservation.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public DateTime FirstPublishedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string FirstPublishedBy { get; set; }
|
||||
|
||||
public DateTime LastPublishedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Non-null when explicitly released by FleetAdmin (audit-logged, requires reason).</summary>
|
||||
public DateTime? ReleasedAt { get; set; }
|
||||
|
||||
public string? ReleasedBy { get; set; }
|
||||
|
||||
public string? ReleaseReason { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Maps an LDAP group to an <see cref="AdminRole"/> for Admin UI access. Optionally scoped
|
||||
/// to one <see cref="ClusterId"/>; when <see cref="IsSystemWide"/> is true, the grant
|
||||
/// applies fleet-wide.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Per <c>docs/v2/plan.md</c> decisions #105 and #150 — this entity is <b>control-plane
|
||||
/// only</b>. The OPC UA data-path evaluator does not read these rows; it reads
|
||||
/// <see cref="NodeAcl"/> joined directly against the session's resolved LDAP group
|
||||
/// memberships. Collapsing the two would let a user inherit tag permissions via an
|
||||
/// admin-role claim path never intended as a data-path grant.</para>
|
||||
///
|
||||
/// <para>Uniqueness: <c>(LdapGroup, ClusterId)</c> — the same LDAP group may hold
|
||||
/// different roles on different clusters, but only one row per cluster. A system-wide row
|
||||
/// (<c>IsSystemWide = true</c>, <c>ClusterId = null</c>) stacks additively with any
|
||||
/// cluster-scoped rows for the same group.</para>
|
||||
/// </remarks>
|
||||
public sealed class LdapGroupRoleMapping
|
||||
{
|
||||
/// <summary>Surrogate primary key.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LDAP group DN the membership query returns (e.g. <c>cn=fleet-admin,ou=groups,dc=corp,dc=example</c>).
|
||||
/// Comparison is case-insensitive per LDAP conventions.
|
||||
/// </summary>
|
||||
public required string LdapGroup { get; set; }
|
||||
|
||||
/// <summary>Admin role this group grants.</summary>
|
||||
public required AdminRole Role { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cluster the grant applies to; <c>null</c> when <see cref="IsSystemWide"/> is true.
|
||||
/// Foreign key to <see cref="ServerCluster.ClusterId"/>.
|
||||
/// </summary>
|
||||
public string? ClusterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> = grant applies across every cluster in the fleet; <c>ClusterId</c> must be null.
|
||||
/// <c>false</c> = grant is cluster-scoped; <c>ClusterId</c> must be populated.
|
||||
/// </summary>
|
||||
public required bool IsSystemWide { get; set; }
|
||||
|
||||
/// <summary>Row creation timestamp (UTC).</summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Optional human-readable note (e.g. "added 2026-04-19 for Warsaw fleet admin handoff").</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Navigation for EF core when the row is cluster-scoped.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA namespace served by a cluster. Generation-versioned per decision #123 —
|
||||
/// namespaces are content (affect what consumers see at the endpoint), not topology.
|
||||
/// </summary>
|
||||
public sealed class Namespace
|
||||
{
|
||||
public Guid NamespaceRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID across generations, e.g. "LINE3-OPCUA-equipment".</summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required NamespaceKind Kind { get; set; }
|
||||
|
||||
/// <summary>E.g. "urn:zb:warsaw-west:equipment". Unique fleet-wide per generation.</summary>
|
||||
public required string NamespaceUri { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
32
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/NodeAcl.cs
Normal file
32
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/NodeAcl.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// One ACL grant: an LDAP group gets a set of <see cref="NodePermissions"/> at a specific scope.
|
||||
/// Generation-versioned per decision #130. See <c>acl-design.md</c> for evaluation algorithm.
|
||||
/// </summary>
|
||||
public sealed class NodeAcl
|
||||
{
|
||||
public Guid NodeAclRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string NodeAclId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required string LdapGroup { get; set; }
|
||||
|
||||
public required NodeAclScopeKind ScopeKind { get; set; }
|
||||
|
||||
/// <summary>NULL when <see cref="ScopeKind"/> = <see cref="NodeAclScopeKind.Cluster"/>; otherwise the scoped entity's logical ID.</summary>
|
||||
public string? ScopeId { get; set; }
|
||||
|
||||
/// <summary>Bitmask of <see cref="NodePermissions"/>. Stored as int in SQL.</summary>
|
||||
public required NodePermissions PermissionFlags { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>Driver-scoped polling group. Tags reference it via <see cref="Tag.PollGroupId"/>.</summary>
|
||||
public sealed class PollGroup
|
||||
{
|
||||
public Guid PollGroupRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string PollGroupId { get; set; }
|
||||
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
public int IntervalMs { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
38
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Script.cs
Normal file
38
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Script.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Per Phase 7 plan decision #8 — user-authored C# script source, referenced by
|
||||
/// <see cref="VirtualTag"/> and <see cref="ScriptedAlarm"/>. One row per script,
|
||||
/// per generation. <c>SourceHash</c> is the compile-cache key.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Scripts are generation-scoped: a draft's edit creates a new row in the draft
|
||||
/// generation, the old row stays frozen in the published generation. Shape mirrors
|
||||
/// the other generation-scoped entities (Equipment, Tag, etc.) — <c>ScriptId</c> is
|
||||
/// the stable logical id that carries across generations; <c>ScriptRowId</c> is the
|
||||
/// row identity.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class Script
|
||||
{
|
||||
public Guid ScriptRowId { get; set; }
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id. Carries across generations.</summary>
|
||||
public required string ScriptId { get; set; }
|
||||
|
||||
/// <summary>Operator-friendly name for log filtering + Admin UI list view.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Raw C# source. Size bounded by the DB column (nvarchar(max)).</summary>
|
||||
public required string SourceCode { get; set; }
|
||||
|
||||
/// <summary>SHA-256 of <see cref="SourceCode"/> — compile-cache key for Phase 7 Stream A's <c>CompiledScriptCache</c>.</summary>
|
||||
public required string SourceHash { get; set; }
|
||||
|
||||
/// <summary>Language — always "CSharp" today; placeholder for future engines (Python/Lua).</summary>
|
||||
public string Language { get; set; } = "CSharp";
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Per Phase 7 plan decisions #5, #13, #15 — a scripted OPC UA Part 9 alarm whose
|
||||
/// condition is the predicate <see cref="Script"/> referenced by
|
||||
/// <see cref="PredicateScriptId"/>. Materialized by <c>Core.ScriptedAlarms</c> as a
|
||||
/// concrete <c>AlarmConditionType</c> subtype per <see cref="AlarmType"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Message tokens (<c>{TagPath}</c>) resolved at emission time per plan decision #13.
|
||||
/// <see cref="HistorizeToAveva"/> (plan decision #15) gates whether transitions
|
||||
/// route through the Core.AlarmHistorian SQLite queue + Galaxy.Host to the Aveva
|
||||
/// Historian alarm schema.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ScriptedAlarm
|
||||
{
|
||||
public Guid ScriptedAlarmRowId { get; set; }
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id — drives <c>AlarmConditionType.ConditionName</c>.</summary>
|
||||
public required string ScriptedAlarmId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Equipment.EquipmentId"/> — owner of this alarm.</summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>Operator-facing alarm name.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Concrete Part 9 type — "AlarmCondition" / "LimitAlarm" / "OffNormalAlarm" / "DiscreteAlarm".</summary>
|
||||
public required string AlarmType { get; set; }
|
||||
|
||||
/// <summary>Numeric severity 1..1000 per OPC UA Part 9 (usual bands: 1-250 Low, 251-500 Medium, 501-750 High, 751-1000 Critical).</summary>
|
||||
public int Severity { get; set; } = 500;
|
||||
|
||||
/// <summary>Template with <c>{TagPath}</c> tokens resolved at emission time.</summary>
|
||||
public required string MessageTemplate { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Script.ScriptId"/> — predicate script returning <c>bool</c>.</summary>
|
||||
public required string PredicateScriptId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plan decision #15 — when true, transitions route through the SQLite store-and-forward
|
||||
/// queue to the Aveva Historian. Defaults on for scripted alarms because they are the
|
||||
/// primary motivation for the historian sink; operator can disable per alarm.
|
||||
/// </summary>
|
||||
public bool HistorizeToAveva { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA Part 9 <c>Retain</c> flag — whether the alarm keeps active-state between
|
||||
/// sessions. Most plant alarms are retained; one-shot event-style alarms are not.
|
||||
/// </summary>
|
||||
public bool Retain { get; set; } = true;
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Per Phase 7 plan decision #14 — persistent runtime state for each scripted alarm.
|
||||
/// Survives process restart so operators don't re-ack and ack history survives for
|
||||
/// GxP / 21 CFR Part 11 compliance. Keyed on <c>ScriptedAlarmId</c> logically (not
|
||||
/// per-generation) because ack state follows the alarm's stable identity across
|
||||
/// generations — a Modified alarm keeps its ack history.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <c>ActiveState</c> is deliberately NOT persisted — it rederives from the current
|
||||
/// predicate evaluation on startup. Only operator-supplied state (<see cref="AckedState"/>,
|
||||
/// <see cref="ConfirmedState"/>, <see cref="ShelvingState"/>) + audit trail persist.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="CommentsJson"/> is an append-only JSON array of <c>{user, utc, text}</c>
|
||||
/// tuples — one per operator comment. Core.ScriptedAlarms' <c>AlarmConditionState.Comments</c>
|
||||
/// serializes directly into this column.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ScriptedAlarmState
|
||||
{
|
||||
/// <summary>Logical FK — matches <see cref="ScriptedAlarm.ScriptedAlarmId"/>. One row per alarm identity.</summary>
|
||||
public required string ScriptedAlarmId { get; set; }
|
||||
|
||||
/// <summary>Enabled/Disabled. Persists across restart per plan decision #14.</summary>
|
||||
public required string EnabledState { get; set; } = "Enabled";
|
||||
|
||||
/// <summary>Unacknowledged / Acknowledged.</summary>
|
||||
public required string AckedState { get; set; } = "Unacknowledged";
|
||||
|
||||
/// <summary>Unconfirmed / Confirmed.</summary>
|
||||
public required string ConfirmedState { get; set; } = "Unconfirmed";
|
||||
|
||||
/// <summary>Unshelved / OneShotShelved / TimedShelved.</summary>
|
||||
public required string ShelvingState { get; set; } = "Unshelved";
|
||||
|
||||
/// <summary>When a TimedShelve expires — null if not shelved or OneShotShelved.</summary>
|
||||
public DateTime? ShelvingExpiresUtc { get; set; }
|
||||
|
||||
/// <summary>User who last acknowledged. Null if never acked.</summary>
|
||||
public string? LastAckUser { get; set; }
|
||||
|
||||
/// <summary>Operator-supplied ack comment. Null if no comment or never acked.</summary>
|
||||
public string? LastAckComment { get; set; }
|
||||
|
||||
public DateTime? LastAckUtc { get; set; }
|
||||
|
||||
/// <summary>User who last confirmed.</summary>
|
||||
public string? LastConfirmUser { get; set; }
|
||||
|
||||
public string? LastConfirmComment { get; set; }
|
||||
|
||||
public DateTime? LastConfirmUtc { get; set; }
|
||||
|
||||
/// <summary>JSON array of operator comments, append-only (GxP audit).</summary>
|
||||
public string CommentsJson { get; set; } = "[]";
|
||||
|
||||
/// <summary>Row write timestamp — tracks last state change.</summary>
|
||||
public DateTime UpdatedAtUtc { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Top-level deployment unit. 1 or 2 <see cref="ClusterNode"/> members.
|
||||
/// Per <c>config-db-schema.md</c> ServerCluster table.
|
||||
/// </summary>
|
||||
public sealed class ServerCluster
|
||||
{
|
||||
/// <summary>Stable logical ID, e.g. "LINE3-OPCUA".</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>UNS level 1. Canonical org value: "zb" per decision #140.</summary>
|
||||
public required string Enterprise { get; set; }
|
||||
|
||||
/// <summary>UNS level 2, e.g. "warsaw-west".</summary>
|
||||
public required string Site { get; set; }
|
||||
|
||||
public byte NodeCount { get; set; }
|
||||
|
||||
public required RedundancyMode RedundancyMode { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
public string? ModifiedBy { get; set; }
|
||||
|
||||
// Navigation
|
||||
public ICollection<ClusterNode> Nodes { get; set; } = [];
|
||||
public ICollection<Namespace> Namespaces { get; set; } = [];
|
||||
public ICollection<ConfigGeneration> Generations { get; set; } = [];
|
||||
}
|
||||
47
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs
Normal file
47
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// One canonical tag (signal) in a cluster's generation. Per decision #110:
|
||||
/// <see cref="EquipmentId"/> is REQUIRED when the driver is in an Equipment-kind namespace
|
||||
/// and NULL when in SystemPlatform-kind namespace (Galaxy hierarchy preserved).
|
||||
/// </summary>
|
||||
public sealed class Tag
|
||||
{
|
||||
public Guid TagRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string TagId { get; set; }
|
||||
|
||||
public required string DriverInstanceId { get; set; }
|
||||
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required when driver is in Equipment-kind namespace; NULL when in SystemPlatform-kind.
|
||||
/// Cross-table invariant enforced by sp_ValidateDraft (decision #110).
|
||||
/// </summary>
|
||||
public string? EquipmentId { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Only used when <see cref="EquipmentId"/> is NULL (SystemPlatform namespace).</summary>
|
||||
public string? FolderPath { get; set; }
|
||||
|
||||
/// <summary>OPC UA built-in type name (Boolean / Int32 / Float / etc.).</summary>
|
||||
public required string DataType { get; set; }
|
||||
|
||||
public required TagAccessLevel AccessLevel { get; set; }
|
||||
|
||||
/// <summary>Per decisions #44–45 — opt-in for write retry eligibility.</summary>
|
||||
public bool WriteIdempotent { get; set; }
|
||||
|
||||
public string? PollGroupId { get; set; }
|
||||
|
||||
/// <summary>Register address / scaling / poll group / byte-order / etc. — schemaless per driver type.</summary>
|
||||
public required string TagConfig { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
21
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsArea.cs
Normal file
21
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsArea.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>UNS level-3 segment. Generation-versioned per decision #115.</summary>
|
||||
public sealed class UnsArea
|
||||
{
|
||||
public Guid UnsAreaRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string UnsAreaId { get; set; }
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>UNS level 3 segment: matches <c>^[a-z0-9-]{1,32}$</c> OR equals literal <c>_default</c>.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
21
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsLine.cs
Normal file
21
src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsLine.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>UNS level-4 segment. Generation-versioned per decision #115.</summary>
|
||||
public sealed class UnsLine
|
||||
{
|
||||
public Guid UnsLineRowId { get; set; }
|
||||
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
public required string UnsLineId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="UnsArea.UnsAreaId"/>; resolved within the same generation.</summary>
|
||||
public required string UnsAreaId { get; set; }
|
||||
|
||||
/// <summary>UNS level 4 segment: matches <c>^[a-z0-9-]{1,32}$</c> OR equals literal <c>_default</c>.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Per Phase 7 plan decision #2 — a virtual (calculated) tag that lives in the
|
||||
/// Equipment tree alongside driver tags. Value is produced by the
|
||||
/// <see cref="Script"/> referenced by <see cref="ScriptId"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="EquipmentId"/> is mandatory — virtual tags are always scoped to an
|
||||
/// Equipment node per plan decision #2 (unified Equipment tree, not a separate
|
||||
/// /Virtual namespace). <see cref="DataType"/> matches the shape used by
|
||||
/// <c>Tag.DataType</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="ChangeTriggered"/> and <see cref="TimerIntervalMs"/> together realize
|
||||
/// plan decision #3 (change + timer). At least one must produce evaluations; the
|
||||
/// Core.VirtualTags engine rejects an all-disabled tag at load time.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class VirtualTag
|
||||
{
|
||||
public Guid VirtualTagRowId { get; set; }
|
||||
public long GenerationId { get; set; }
|
||||
|
||||
/// <summary>Stable logical id.</summary>
|
||||
public required string VirtualTagId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Equipment.EquipmentId"/> — owner of this virtual tag.</summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>Browse name — unique within owning Equipment.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>DataType string — same vocabulary as <see cref="Tag.DataType"/>.</summary>
|
||||
public required string DataType { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Script.ScriptId"/> — the script that computes this tag's value.</summary>
|
||||
public required string ScriptId { get; set; }
|
||||
|
||||
/// <summary>Re-evaluate when any referenced input tag changes. Default on.</summary>
|
||||
public bool ChangeTriggered { get; set; } = true;
|
||||
|
||||
/// <summary>Timer re-evaluation cadence in milliseconds. <c>null</c> = no timer.</summary>
|
||||
public int? TimerIntervalMs { get; set; }
|
||||
|
||||
/// <summary>Per plan decision #10 — checkbox to route this tag's values through <c>IHistoryWriter</c>.</summary>
|
||||
public bool Historize { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user