9cad9ed0fc
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
96 lines
5.1 KiB
C#
96 lines
5.1 KiB
C#
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.OpcUa;
|
|
|
|
/// <summary>
|
|
/// Phase 6.4 Stream D: materializes the OPC 40010 Machinery companion-spec Identification
|
|
/// sub-folder under an Equipment node. Reads the nine documented columns off the
|
|
/// <see cref="Equipment"/> row and emits one property per non-null field.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Pure-function shape — testable without a real OPC UA node manager. The caller
|
|
/// passes the builder scoped to the Equipment node; this class handles the Identification
|
|
/// sub-folder creation + per-field <see cref="IAddressSpaceBuilder.AddProperty"/> calls.</para>
|
|
///
|
|
/// <para>ACL binding: the sub-folder + its properties inherit the Equipment scope's
|
|
/// grants (no new scope level). Phase 6.2's trie treats them as part of the Equipment
|
|
/// ScopeId — a user with Equipment-level grant reads Identification; a user without the
|
|
/// grant gets BadUserAccessDenied on both the Equipment node + its Identification variables.
|
|
/// See <c>docs/v2/acl-design.md</c> §Identification cross-reference.</para>
|
|
///
|
|
/// <para>The nine fields are exposed exactly when they carry a non-null
|
|
/// value. A row with all nine null produces no Identification sub-folder at all — the
|
|
/// caller can use <see cref="HasAnyFields(Equipment)"/> to skip the Folder call entirely
|
|
/// and avoid a pointless empty folder appearing in browse trees.</para>
|
|
/// </remarks>
|
|
public static class IdentificationFolderBuilder
|
|
{
|
|
/// <summary>Browse + display name of the sub-folder — fixed per OPC 40010 convention.</summary>
|
|
public const string FolderName = "Identification";
|
|
|
|
/// <summary>
|
|
/// Canonical decision #139 field set exposed in the Identification sub-folder. Order
|
|
/// matches the decision-log entry so any browse-order reader can cross-reference
|
|
/// without re-sorting.
|
|
/// </summary>
|
|
public static IReadOnlyList<string> FieldNames { get; } = new[]
|
|
{
|
|
"Manufacturer", "Model", "SerialNumber",
|
|
"HardwareRevision", "SoftwareRevision",
|
|
"YearOfConstruction", "AssetLocation",
|
|
"ManufacturerUri", "DeviceManualUri",
|
|
};
|
|
|
|
/// <summary>Checks whether the equipment row has at least one non-null Identification field.</summary>
|
|
/// <param name="equipment">The equipment entity.</param>
|
|
/// <returns>True if any Identification field is non-null; otherwise false.</returns>
|
|
public static bool HasAnyFields(Equipment equipment)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(equipment);
|
|
return equipment.Manufacturer is not null
|
|
|| equipment.Model is not null
|
|
|| equipment.SerialNumber is not null
|
|
|| equipment.HardwareRevision is not null
|
|
|| equipment.SoftwareRevision is not null
|
|
|| equipment.YearOfConstruction is not null
|
|
|| equipment.AssetLocation is not null
|
|
|| equipment.ManufacturerUri is not null
|
|
|| equipment.DeviceManualUri is not null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the Identification sub-folder under the given equipment builder. Returns the sub-folder builder
|
|
/// (or null when no fields are present) so callers can attach additional nodes if needed.
|
|
/// </summary>
|
|
/// <param name="equipmentBuilder">The equipment address space builder.</param>
|
|
/// <param name="equipment">The equipment entity with identification fields.</param>
|
|
/// <returns>The sub-folder builder, or null if no fields are present.</returns>
|
|
public static IAddressSpaceBuilder? Build(IAddressSpaceBuilder equipmentBuilder, Equipment equipment)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(equipmentBuilder);
|
|
ArgumentNullException.ThrowIfNull(equipment);
|
|
|
|
if (!HasAnyFields(equipment)) return null;
|
|
|
|
var folder = equipmentBuilder.Folder(FolderName, FolderName);
|
|
AddIfPresent(folder, "Manufacturer", DriverDataType.String, equipment.Manufacturer);
|
|
AddIfPresent(folder, "Model", DriverDataType.String, equipment.Model);
|
|
AddIfPresent(folder, "SerialNumber", DriverDataType.String, equipment.SerialNumber);
|
|
AddIfPresent(folder, "HardwareRevision", DriverDataType.String, equipment.HardwareRevision);
|
|
AddIfPresent(folder, "SoftwareRevision", DriverDataType.String, equipment.SoftwareRevision);
|
|
AddIfPresent(folder, "YearOfConstruction", DriverDataType.Int32,
|
|
equipment.YearOfConstruction is null ? null : (object)(int)equipment.YearOfConstruction.Value);
|
|
AddIfPresent(folder, "AssetLocation", DriverDataType.String, equipment.AssetLocation);
|
|
AddIfPresent(folder, "ManufacturerUri", DriverDataType.String, equipment.ManufacturerUri);
|
|
AddIfPresent(folder, "DeviceManualUri", DriverDataType.String, equipment.DeviceManualUri);
|
|
return folder;
|
|
}
|
|
|
|
private static void AddIfPresent(IAddressSpaceBuilder folder, string name, DriverDataType dataType, object? value)
|
|
{
|
|
if (value is null) return;
|
|
folder.AddProperty(name, dataType, value);
|
|
}
|
|
}
|