diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs index 27cfbfff..72fafc22 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs @@ -58,7 +58,28 @@ public sealed record AddressSpaceComposition( public sealed record UnsAreaProjection(string UnsAreaId, string DisplayName); public sealed record UnsLineProjection(string UnsLineId, string UnsAreaId, string DisplayName); -public sealed record EquipmentNode(string EquipmentId, string DisplayName, string UnsLineId); + +/// One UNS level-5 equipment folder in the address space. is the +/// logical NodeId; is the friendly UNS Name segment; +/// is the parent line the folder hangs under. +/// / carry the equipment's optional bindings +/// (both null ⇒ driver-less / no device), copied straight from the Equipment row. +/// is the device's connection host (e.g. "10.0.0.5:8193") resolved from the +/// bound Device's schemaless DeviceConfig JSON via +/// null when there is no device, no +/// HostAddress in its config, or the host cannot be parsed. These three let a later task graft a +/// driver's discovered FixedTree onto an equipment that has zero authored tags, and partition a +/// multi-device driver by host. The value is normalized identically on both the live-edit composer and +/// the artifact-decode sides (single source of truth: ); +/// the later partition task MUST normalize the driver-discovered device-host folder segment the same way +/// (trim + lower-case) so the two compare equal. +public sealed record EquipmentNode( + string EquipmentId, + string DisplayName, + string UnsLineId, + string? DriverInstanceId = null, + string? DeviceId = null, + string? DeviceHost = null); public sealed record DriverInstancePlan(string DriverInstanceId, string DriverType, string ConfigJson); public sealed record ScriptedAlarmPlan(string ScriptedAlarmId, string EquipmentId, string PredicateScriptId, string MessageTemplate); @@ -277,15 +298,17 @@ public static class AddressSpaceComposer /// The equipment. /// The driver instances. /// The scripted alarms. + /// The per-device rows used to resolve each equipment's DeviceHost. null = none. /// The composition result. public static AddressSpaceComposition Compose( IReadOnlyList unsAreas, IReadOnlyList unsLines, IReadOnlyList equipment, IReadOnlyList driverInstances, - IReadOnlyList scriptedAlarms) => + IReadOnlyList scriptedAlarms, + IReadOnlyList? devices = null) => Compose(unsAreas, unsLines, equipment, driverInstances, scriptedAlarms, - Array.Empty(), Array.Empty()); + Array.Empty(), Array.Empty(), devices: devices); /// /// Composes the address space build plan from the configuration entities. @@ -299,6 +322,8 @@ public static class AddressSpaceComposer /// The namespaces. /// The Equipment-namespace virtual (calculated) tags. null = none. /// The scripts joined to by ScriptId for the expression. null = none. + /// The per-device rows (DeviceId + schemaless DeviceConfig JSON) used to resolve + /// each equipment's DeviceHost from its bound DeviceId. null = none. /// The composition result. public static AddressSpaceComposition Compose( IReadOnlyList unsAreas, @@ -309,10 +334,19 @@ public static class AddressSpaceComposer IReadOnlyList tags, IReadOnlyList namespaces, IReadOnlyList? virtualTags = null, - IReadOnlyList