feat(otopcua): EquipmentNode carries DriverInstanceId/DeviceId/DeviceHost (follow-up E projection)
This commit is contained in:
@@ -201,7 +201,10 @@ public static class DeploymentArtifact
|
||||
|
||||
var areas = ReadArray(root, "UnsAreas", ReadAreaProjection);
|
||||
var lines = ReadArray(root, "UnsLines", ReadLineProjection);
|
||||
var equipment = ReadArray(root, "Equipment", ReadEquipmentNode);
|
||||
// DeviceId → connection host, resolved from the artifact's Devices array via the SAME shared
|
||||
// helper the composer uses, so each EquipmentNode.DeviceHost is byte-parity-equal across seams.
|
||||
var deviceHostById = BuildDeviceHostMap(root);
|
||||
var equipment = ReadArray(root, "Equipment", el => ReadEquipmentNode(el, deviceHostById));
|
||||
var drivers = ReadArray(root, "DriverInstances", ReadDriverPlan);
|
||||
var alarms = ReadArray(root, "ScriptedAlarms", ReadAlarmPlan);
|
||||
var equipmentTags = BuildEquipmentTagPlans(root);
|
||||
@@ -807,7 +810,29 @@ public static class DeploymentArtifact
|
||||
return new UnsLineProjection(id!, areaId!, name ?? id!);
|
||||
}
|
||||
|
||||
private static EquipmentNode? ReadEquipmentNode(JsonElement el)
|
||||
/// <summary>Build the <c>DeviceId</c> → connection-host map from the artifact's <c>Devices</c> array
|
||||
/// (each row carries a <c>DeviceId</c> + schemaless <c>DeviceConfig</c> JSON). The host is resolved via
|
||||
/// the shared <see cref="AddressSpaceComposer.TryExtractDeviceHost"/> so the artifact-decode side
|
||||
/// normalizes byte-identically to the live-edit composer. Ordinal comparer + last-wins on a duplicate
|
||||
/// DeviceId. A missing/empty/non-array <c>Devices</c> property yields an empty map (no device hosts).</summary>
|
||||
/// <param name="root">The artifact root element.</param>
|
||||
/// <returns>The resolved DeviceId → host map (host may be null when a device has no parseable HostAddress).</returns>
|
||||
private static IReadOnlyDictionary<string, string?> BuildDeviceHostMap(JsonElement root)
|
||||
{
|
||||
var map = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||
if (!root.TryGetProperty("Devices", out var arr) || arr.ValueKind != JsonValueKind.Array)
|
||||
return map;
|
||||
foreach (var el in arr.EnumerateArray())
|
||||
{
|
||||
if (el.ValueKind != JsonValueKind.Object) continue;
|
||||
var deviceId = ReadString(el, "DeviceId");
|
||||
if (string.IsNullOrWhiteSpace(deviceId)) continue;
|
||||
map[deviceId!] = AddressSpaceComposer.TryExtractDeviceHost(ReadString(el, "DeviceConfig"));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static EquipmentNode? ReadEquipmentNode(JsonElement el, IReadOnlyDictionary<string, string?> deviceHostById)
|
||||
{
|
||||
var id = ReadString(el, "EquipmentId");
|
||||
// DisplayName = the UNS level-5 Name segment (friendly browse name, matching UnsArea/UnsLine
|
||||
@@ -816,7 +841,19 @@ public static class DeploymentArtifact
|
||||
var displayName = ReadString(el, "Name");
|
||||
var lineId = ReadString(el, "UnsLineId");
|
||||
if (string.IsNullOrWhiteSpace(id)) return null;
|
||||
return new EquipmentNode(id!, displayName ?? id!, lineId ?? string.Empty);
|
||||
// DriverInstanceId / DeviceId copied straight from the row (null when absent / JSON null);
|
||||
// DeviceHost resolved from the device-host map by DeviceId — byte-parity with the composer's
|
||||
// `e.DeviceId is null ? null : deviceHostById.GetValueOrDefault(e.DeviceId)`.
|
||||
var driverInstanceId = ReadString(el, "DriverInstanceId");
|
||||
var deviceId = ReadString(el, "DeviceId");
|
||||
var deviceHost = deviceId is null ? null : deviceHostById.GetValueOrDefault(deviceId);
|
||||
return new EquipmentNode(
|
||||
id!,
|
||||
displayName ?? id!,
|
||||
lineId ?? string.Empty,
|
||||
DriverInstanceId: driverInstanceId,
|
||||
DeviceId: deviceId,
|
||||
DeviceHost: deviceHost);
|
||||
}
|
||||
|
||||
private static DriverInstancePlan? ReadDriverPlan(JsonElement el)
|
||||
|
||||
Reference in New Issue
Block a user