v3 batch1 contracts: RawPaths identity helper + reshaped greenfield entities
Shared-type foundation for Batch 1 fan-out (coordinator contracts commit): - New Commons/Types/RawPaths.cs — the v3 identity authority (Build/Combine/ Split/Leaf/TryParent/ValidateSegment, ordinal comparer). - New entities RawFolder, TagGroup, UnsTagReference. - Reshape DriverInstance (drop NamespaceId, add nullable RawFolderId), Tag (require DeviceId, add nullable TagGroupId, drop DriverInstanceId/ EquipmentId/FolderPath, keep WriteIdempotent), Equipment (drop DriverInstanceId/DeviceId), Device (doc: now universal), ServerCluster (nav Namespaces -> RawFolders). - Delete Namespace, EquipmentImportBatch(+Row), NamespaceKind enum. - DriverTypeMetadata: drop AllowedNamespaceKinds + NamespaceKindCompatibility. Solution is intentionally red below the foundation projects until Wave C integrates. Commons + Core.Abstractions compile green.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
|
||||
/// <summary>
|
||||
/// The v3 identity authority. A <c>RawPath</c> is the cluster-scoped, slash-separated raw device
|
||||
/// path — <c><Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Tag></c> — and is
|
||||
/// the single string used at every identity seam: <c>ns=Raw</c> NodeIds, the driver wire-reference
|
||||
/// (<c>EquipmentTagRefResolver</c> key), the value fan-out / write-routing maps
|
||||
/// (<c>DriverHostActor</c>), the historian default tagname + mux interest key, the native-alarm
|
||||
/// <c>ConditionId</c>, and <c>ctx.GetTag</c> script paths.
|
||||
/// <para>
|
||||
/// RawPaths are ALWAYS built through this helper — never string-concatenated ad hoc — so the
|
||||
/// separator and the segment charset are enforced in exactly one place. Comparison is
|
||||
/// case-sensitive ordinal everywhere (see <see cref="Comparer"/>).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class RawPaths
|
||||
{
|
||||
/// <summary>The single path separator character. Forbidden inside any segment.</summary>
|
||||
public const char Separator = '/';
|
||||
|
||||
/// <summary>The path separator as a string (join/split convenience).</summary>
|
||||
public const string SeparatorString = "/";
|
||||
|
||||
/// <summary>The one comparer for RawPaths and their segments: case-sensitive ordinal.</summary>
|
||||
public static StringComparer Comparer => StringComparer.Ordinal;
|
||||
|
||||
/// <summary>
|
||||
/// Validate a single path segment. A segment must be non-empty, contain no
|
||||
/// <see cref="Separator"/>, and carry no leading/trailing whitespace (the separator and
|
||||
/// surrounding whitespace would corrupt the NodeId / round-trip).
|
||||
/// </summary>
|
||||
/// <param name="segment">The candidate segment (a folder / driver / device / group / tag name).</param>
|
||||
/// <returns>A human-readable error when invalid, or <see langword="null"/> when the segment is valid.</returns>
|
||||
public static string? ValidateSegment(string? segment)
|
||||
{
|
||||
if (string.IsNullOrEmpty(segment))
|
||||
return "Name must not be empty.";
|
||||
if (segment.IndexOf(Separator) >= 0)
|
||||
return $"Name must not contain '{Separator}'.";
|
||||
if (char.IsWhiteSpace(segment[0]) || char.IsWhiteSpace(segment[^1]))
|
||||
return "Name must not have leading or trailing whitespace.";
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>True when <paramref name="segment"/> passes <see cref="ValidateSegment"/>.</summary>
|
||||
/// <param name="segment">The candidate segment.</param>
|
||||
/// <returns><see langword="true"/> when the segment is a legal RawPath segment.</returns>
|
||||
public static bool IsValidSegment(string? segment) => ValidateSegment(segment) is null;
|
||||
|
||||
/// <summary>
|
||||
/// Build a RawPath from ordered segments (root-first). Every segment is validated via
|
||||
/// <see cref="ValidateSegment"/>; an invalid or empty segment list throws.
|
||||
/// </summary>
|
||||
/// <param name="segments">The ordered, non-empty segments (root → leaf).</param>
|
||||
/// <returns>The slash-joined RawPath.</returns>
|
||||
/// <exception cref="ArgumentException">A segment is invalid, or the sequence is empty.</exception>
|
||||
public static string Build(IEnumerable<string> segments)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(segments);
|
||||
var list = segments as IReadOnlyList<string> ?? segments.ToList();
|
||||
if (list.Count == 0)
|
||||
throw new ArgumentException("A RawPath must have at least one segment.", nameof(segments));
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
var error = ValidateSegment(list[i]);
|
||||
if (error is not null)
|
||||
throw new ArgumentException($"Invalid RawPath segment at index {i} ('{list[i]}'): {error}", nameof(segments));
|
||||
}
|
||||
|
||||
return string.Join(Separator, list);
|
||||
}
|
||||
|
||||
/// <summary>Build a RawPath from ordered segments (root-first). See <see cref="Build(IEnumerable{string})"/>.</summary>
|
||||
/// <param name="segments">The ordered, non-empty segments (root → leaf).</param>
|
||||
/// <returns>The slash-joined RawPath.</returns>
|
||||
public static string Build(params string[] segments) => Build((IEnumerable<string>)segments);
|
||||
|
||||
/// <summary>
|
||||
/// Append a child segment to an existing (already-built) parent RawPath. The child is
|
||||
/// validated; a blank parent yields the (validated) child alone.
|
||||
/// </summary>
|
||||
/// <param name="parentPath">The parent RawPath (assumed already valid), or blank for a root segment.</param>
|
||||
/// <param name="childSegment">The child segment to append.</param>
|
||||
/// <returns>The combined RawPath.</returns>
|
||||
/// <exception cref="ArgumentException">The child segment is invalid.</exception>
|
||||
public static string Combine(string? parentPath, string childSegment)
|
||||
{
|
||||
var error = ValidateSegment(childSegment);
|
||||
if (error is not null)
|
||||
throw new ArgumentException($"Invalid RawPath segment ('{childSegment}'): {error}", nameof(childSegment));
|
||||
|
||||
return string.IsNullOrEmpty(parentPath) ? childSegment : parentPath + Separator + childSegment;
|
||||
}
|
||||
|
||||
/// <summary>Split a RawPath into its ordered segments (root → leaf).</summary>
|
||||
/// <param name="rawPath">The RawPath to split.</param>
|
||||
/// <returns>The ordered segments; a single-element array for a one-segment path.</returns>
|
||||
public static IReadOnlyList<string> Split(string rawPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rawPath);
|
||||
return rawPath.Split(Separator);
|
||||
}
|
||||
|
||||
/// <summary>The leaf (last) segment of a RawPath.</summary>
|
||||
/// <param name="rawPath">The RawPath.</param>
|
||||
/// <returns>The final segment.</returns>
|
||||
public static string Leaf(string rawPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rawPath);
|
||||
var idx = rawPath.LastIndexOf(Separator);
|
||||
return idx < 0 ? rawPath : rawPath[(idx + 1)..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the parent RawPath (everything up to, not including, the leaf). Returns
|
||||
/// <see langword="false"/> for a single-segment path (no parent).
|
||||
/// </summary>
|
||||
/// <param name="rawPath">The RawPath.</param>
|
||||
/// <param name="parent">The parent RawPath when this returns <see langword="true"/>.</param>
|
||||
/// <returns><see langword="true"/> when a parent exists.</returns>
|
||||
public static bool TryParent(string rawPath, out string parent)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rawPath);
|
||||
var idx = rawPath.LastIndexOf(Separator);
|
||||
if (idx < 0)
|
||||
{
|
||||
parent = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
parent = rawPath[..idx];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>Per-device row for multi-device drivers (Modbus, AB CIP). Optional for single-device drivers.</summary>
|
||||
/// <summary>
|
||||
/// v3: a device under a <see cref="DriverInstance"/>. Now UNIVERSAL — every driver has ≥1 device
|
||||
/// (single-endpoint drivers get an auto-created default device). Endpoint/connection settings live
|
||||
/// in <see cref="DeviceConfig"/> (the Kepware channel/device split: <c>DriverConfig</c> keeps
|
||||
/// protocol/channel-level settings; <c>DeviceConfig</c> carries host/endpoint + per-device
|
||||
/// settings). The device name is a RawPath segment.
|
||||
/// </summary>
|
||||
public sealed class Device
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -13,12 +13,13 @@ public sealed class DriverInstance
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logical FK to <see cref="Namespace.NamespaceId"/>. Same-cluster binding enforced by
|
||||
/// <c>sp_ValidateDraft</c>: Namespace.ClusterId must equal DriverInstance.ClusterId.
|
||||
/// v3: logical FK to a containing <see cref="RawFolder.RawFolderId"/>; <see langword="null"/>
|
||||
/// = the driver sits at the cluster root of the Raw tree. Contributes the leading segments of
|
||||
/// every child tag's <c>RawPath</c>.
|
||||
/// </summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
public string? RawFolderId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the friendly name of this driver instance.</summary>
|
||||
/// <summary>Gets or sets the friendly name of this driver instance. A RawPath segment.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Galaxy | Modbus | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient</summary>
|
||||
|
||||
@@ -17,14 +17,9 @@ public sealed class Equipment
|
||||
/// <summary>UUIDv4, IMMUTABLE across all generations of the same EquipmentId. Downstream-consumer join key.</summary>
|
||||
public Guid EquipmentUuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional logical FK to the driver providing data for this equipment.
|
||||
/// <c>null</c> = VirtualTag-only / driver-less equipment (no field driver).
|
||||
/// </summary>
|
||||
public string? DriverInstanceId { get; set; }
|
||||
|
||||
/// <summary>Optional logical FK to a multi-device driver's device.</summary>
|
||||
public string? DeviceId { get; set; }
|
||||
// v3: equipment no longer binds a driver or device. It references existing raw tags via
|
||||
// UnsTagReference and hosts VirtualTags + ScriptedAlarms. The old DriverInstanceId / DeviceId
|
||||
// binding columns are retired.
|
||||
|
||||
/// <summary>Logical FK to <see cref="UnsLine.UnsLineId"/>.</summary>
|
||||
public required string UnsLineId { get; set; }
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
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
|
||||
{
|
||||
/// <summary>Gets or sets the unique identifier for this batch.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the user name who created this batch.</summary>
|
||||
public required string CreatedBy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when this batch was created.</summary>
|
||||
public DateTime CreatedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the total number of rows staged in this batch.</summary>
|
||||
public int RowsStaged { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of rows accepted in this batch.</summary>
|
||||
public int RowsAccepted { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of rows rejected in this batch.</summary>
|
||||
public int RowsRejected { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UTC timestamp when this batch was finalised, or null if still in staging.</summary>
|
||||
public DateTime? FinalisedAtUtc { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the collection of staged rows in this batch.</summary>
|
||||
public ICollection<EquipmentImportRow> Rows { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One staged row under an <see cref="EquipmentImportBatch"/>. Mirrors the required
|
||||
/// + optional 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
|
||||
{
|
||||
/// <summary>Gets or sets the unique identifier for this row.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the parent batch identifier.</summary>
|
||||
public Guid BatchId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the line number in the source file.</summary>
|
||||
public int LineNumberInFile { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether this row was accepted.</summary>
|
||||
public bool IsAccepted { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the reason this row was rejected, if applicable.</summary>
|
||||
public string? RejectReason { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the Z tag identifier.</summary>
|
||||
public required string ZTag { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the machine code.</summary>
|
||||
public required string MachineCode { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the SAP identifier.</summary>
|
||||
public required string SAPID { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment identifier.</summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment UUID.</summary>
|
||||
public required string EquipmentUuid { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment name.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UNS area name.</summary>
|
||||
public required string UnsAreaName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the UNS line name.</summary>
|
||||
public required string UnsLineName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the manufacturer name.</summary>
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the equipment model.</summary>
|
||||
public string? Model { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the serial number.</summary>
|
||||
public string? SerialNumber { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the hardware revision.</summary>
|
||||
public string? HardwareRevision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the software revision.</summary>
|
||||
public string? SoftwareRevision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the year of construction.</summary>
|
||||
public string? YearOfConstruction { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the asset location.</summary>
|
||||
public string? AssetLocation { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the manufacturer URI.</summary>
|
||||
public string? ManufacturerUri { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the device manual URI.</summary>
|
||||
public string? DeviceManualUri { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the parent batch.</summary>
|
||||
public EquipmentImportBatch? Batch { get; set; }
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA namespace served by a cluster. Generation-versioned —
|
||||
/// namespaces are content (affect what consumers see at the endpoint), not topology.
|
||||
/// </summary>
|
||||
public sealed class Namespace
|
||||
{
|
||||
/// <summary>Gets or sets the row identifier for this namespace.</summary>
|
||||
public Guid NamespaceRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID, e.g. "LINE3-OPCUA-equipment". Globally unique in v2.</summary>
|
||||
public required string NamespaceId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cluster identifier.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the namespace kind.</summary>
|
||||
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; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the namespace is enabled.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets optional notes about the namespace.</summary>
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets or sets the associated server cluster.</summary>
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// v3 Raw-tree driver-organizing folder. Nestable (Kepware-style) directly under a cluster or
|
||||
/// under another <see cref="RawFolder"/>. Folders group <see cref="DriverInstance"/> rows; they
|
||||
/// contribute the leading segments of a driver's <c>RawPath</c>.
|
||||
/// </summary>
|
||||
public sealed class RawFolder
|
||||
{
|
||||
/// <summary>Gets or sets the database row identifier (PK).</summary>
|
||||
public Guid RawFolderRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID, unique fleet-wide.</summary>
|
||||
public required string RawFolderId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="ServerCluster.ClusterId"/> — the folder is cluster-rooted.</summary>
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to a parent <see cref="RawFolder.RawFolderId"/>; <see langword="null"/> = root under the cluster.</summary>
|
||||
public string? ParentRawFolderId { get; set; }
|
||||
|
||||
/// <summary>Sibling-unique folder name. A RawPath segment: no <c>/</c>, no lead/trail whitespace.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Sibling display ordering.</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
@@ -47,6 +47,6 @@ public sealed class ServerCluster
|
||||
// Navigation
|
||||
/// <summary>Gets or sets the collection of cluster nodes.</summary>
|
||||
public ICollection<ClusterNode> Nodes { get; set; } = [];
|
||||
/// <summary>Gets or sets the collection of namespaces in the cluster.</summary>
|
||||
public ICollection<Namespace> Namespaces { get; set; } = [];
|
||||
/// <summary>Gets or sets the collection of root-level Raw-tree folders in the cluster.</summary>
|
||||
public ICollection<RawFolder> RawFolders { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -3,46 +3,34 @@ using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// One canonical tag (signal) in a cluster's generation. <see cref="EquipmentId"/> set ⟺ the
|
||||
/// tag participates in the Equipment tree. A <c>GalaxyMxGateway</c>-bound equipment tag is an
|
||||
/// ordinary equipment tag (GalaxyMxGateway is a standard Equipment-kind driver) that carries its
|
||||
/// Galaxy attribute reference in <c>TagConfig.FullName</c> — there is no separate alias concept.
|
||||
/// v3 raw-only tag (signal). A tag is authored ONCE, under a <see cref="Device"/> in the Raw
|
||||
/// tree; its identity is its <c>RawPath</c>
|
||||
/// (<c><Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Name></c>). UNS
|
||||
/// surfaces it by reference (<see cref="UnsTagReference"/>), never by re-authoring. The tag's
|
||||
/// driver-specific address lives inside <see cref="TagConfig"/> as an ordinary field (what the
|
||||
/// driver dials), not as system identity.
|
||||
/// </summary>
|
||||
public sealed class Tag
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique database row identifier for the tag.
|
||||
/// </summary>
|
||||
/// <summary>Gets or sets the unique database row identifier for the tag (PK).</summary>
|
||||
public Guid TagRowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag identifier.
|
||||
/// </summary>
|
||||
/// <summary>Gets or sets the stable logical tag identifier (unique fleet-wide).</summary>
|
||||
public required string TagId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the driver instance identifier for this tag.
|
||||
/// </summary>
|
||||
public required string DriverInstanceId { get; set; }
|
||||
/// <summary>v3: required logical FK to <see cref="Device.DeviceId"/>. Every tag lives under a device.</summary>
|
||||
public required string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device identifier.
|
||||
/// Optional logical FK to a containing <see cref="TagGroup.TagGroupId"/>;
|
||||
/// <see langword="null"/> = the tag sits directly under its device. Contributes the middle
|
||||
/// segments of the tag's <c>RawPath</c>.
|
||||
/// </summary>
|
||||
public string? DeviceId { get; set; }
|
||||
public string? TagGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set when the tag belongs to an Equipment; NULL for FolderPath-scoped (namespace-root) tags.
|
||||
/// </summary>
|
||||
public string? EquipmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tag name.
|
||||
/// </summary>
|
||||
/// <summary>Gets or sets the tag name. A RawPath segment: no <c>/</c>, no lead/trail whitespace.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Only used when <see cref="EquipmentId"/> is NULL (FolderPath-scoped namespace tag).</summary>
|
||||
public string? FolderPath { get; set; }
|
||||
|
||||
/// <summary>OPC UA built-in type name (Boolean / Int32 / Float / etc.).</summary>
|
||||
public required string DataType { get; set; }
|
||||
|
||||
@@ -51,7 +39,7 @@ public sealed class Tag
|
||||
/// </summary>
|
||||
public required TagAccessLevel AccessLevel { get; set; }
|
||||
|
||||
/// <summary>Opt-in for write retry eligibility.</summary>
|
||||
/// <summary>Opt-in for write retry eligibility (R2 resilience contract — retained in v3).</summary>
|
||||
public bool WriteIdempotent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// v3 Raw-tree tag-organizing group. Nestable (Kepware-style) directly under a
|
||||
/// <see cref="Device"/> or under another <see cref="TagGroup"/>. Groups contribute the middle
|
||||
/// segments of a tag's <c>RawPath</c> (between the device and the tag name).
|
||||
/// </summary>
|
||||
public sealed class TagGroup
|
||||
{
|
||||
/// <summary>Gets or sets the database row identifier (PK).</summary>
|
||||
public Guid TagGroupRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID, unique fleet-wide.</summary>
|
||||
public required string TagGroupId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Device.DeviceId"/> — the group lives under a device.</summary>
|
||||
public required string DeviceId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to a parent <see cref="TagGroup.TagGroupId"/>; <see langword="null"/> = root under the device.</summary>
|
||||
public string? ParentTagGroupId { get; set; }
|
||||
|
||||
/// <summary>Sibling-unique group name. A RawPath segment: no <c>/</c>, no lead/trail whitespace.</summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>Sibling display ordering.</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// v3 UNS projection row: a reference from an <see cref="Equipment"/> to an existing raw
|
||||
/// <see cref="Tag"/>. UNS authoring is reference-only — equipment no longer authors
|
||||
/// <c>TagConfig</c> or binds a driver; it points at raw tags (optionally under a display-name
|
||||
/// override). The backing raw tag is the single value source; the UNS node fans out from it.
|
||||
/// </summary>
|
||||
public sealed class UnsTagReference
|
||||
{
|
||||
/// <summary>Gets or sets the database row identifier (PK).</summary>
|
||||
public Guid UnsTagReferenceRowId { get; set; }
|
||||
|
||||
/// <summary>Stable logical ID, unique fleet-wide.</summary>
|
||||
public required string UnsTagReferenceId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Equipment.EquipmentId"/> — the referencing equipment.</summary>
|
||||
public required string EquipmentId { get; set; }
|
||||
|
||||
/// <summary>Logical FK to <see cref="Tag.TagId"/> — the backing raw tag (same cluster as the equipment).</summary>
|
||||
public required string TagId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional UNS display-name override; <see langword="null"/> = use the raw tag's <c>Name</c>.
|
||||
/// The effective name must be unique within the equipment across references, VirtualTags, and
|
||||
/// ScriptedAlarms (enforced at authoring and at the deploy gate).
|
||||
/// </summary>
|
||||
public string? DisplayNameOverride { get; set; }
|
||||
|
||||
/// <summary>Sibling display ordering within the equipment's Tags list.</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
/// <summary>OPC UA namespace kind. One of each kind per cluster per generation.</summary>
|
||||
public enum NamespaceKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Equipment namespace — raw signals from native-protocol drivers (Modbus, AB CIP, AB Legacy,
|
||||
/// S7, TwinCAT, FOCAS, and OpcUaClient when gatewaying raw equipment). UNS 5-level hierarchy
|
||||
/// applies.
|
||||
/// </summary>
|
||||
Equipment,
|
||||
|
||||
/// <summary>
|
||||
/// Reserved for future replay driver per handoff §"Digital Twin Touchpoints" — not populated
|
||||
/// in v2.0 but enum value reserved so the schema does not need to change when the replay
|
||||
/// driver lands.
|
||||
/// </summary>
|
||||
Simulated,
|
||||
}
|
||||
@@ -84,7 +84,6 @@ public sealed class DriverTypeRegistry
|
||||
|
||||
/// <summary>Per-driver-type metadata used by the Core, validator, and Admin UI.</summary>
|
||||
/// <param name="TypeName">Driver type name (matches <c>DriverInstance.DriverType</c> column values).</param>
|
||||
/// <param name="AllowedNamespaceKinds">Which namespace kinds this driver type may be bound to.</param>
|
||||
/// <param name="DriverConfigJsonSchema">JSON Schema (Draft 2020-12) the driver's <c>DriverConfig</c> column must validate against.</param>
|
||||
/// <param name="DeviceConfigJsonSchema">JSON Schema for <c>DeviceConfig</c> (multi-device drivers); null if the driver has no device layer.</param>
|
||||
/// <param name="TagConfigJsonSchema">JSON Schema for <c>TagConfig</c>; required for every driver since every driver has tags.</param>
|
||||
@@ -95,24 +94,11 @@ public sealed class DriverTypeRegistry
|
||||
/// hybrid-formula constants, and whether process-level <c>MemoryRecycle</c> / scheduled-
|
||||
/// recycle protections apply (Tier C only). Every registered driver type must declare one.
|
||||
/// </param>
|
||||
// v3: AllowedNamespaceKinds retired with the Namespace entity — the two OPC UA namespaces (Raw/UNS)
|
||||
// are implicit, so a driver type no longer declares a namespace-kind compatibility bitmask.
|
||||
public sealed record DriverTypeMetadata(
|
||||
string TypeName,
|
||||
NamespaceKindCompatibility AllowedNamespaceKinds,
|
||||
string DriverConfigJsonSchema,
|
||||
string? DeviceConfigJsonSchema,
|
||||
string TagConfigJsonSchema,
|
||||
DriverTier Tier);
|
||||
|
||||
/// <summary>Bitmask of namespace kinds a driver type may populate.</summary>
|
||||
[Flags]
|
||||
public enum NamespaceKindCompatibility
|
||||
{
|
||||
/// <summary>Driver does not populate any namespace (invalid; should never appear in registry).</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Driver may populate Equipment-kind namespaces (UNS path, Equipment rows).</summary>
|
||||
Equipment = 1,
|
||||
|
||||
/// <summary>Driver may populate the future Simulated namespace (replay driver — not in v2.0).</summary>
|
||||
Simulated = 4,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user