diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs index 9e13c71f..e7fcb0e9 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs @@ -12,8 +12,17 @@ public record MgmtDeleteInstanceCommand(int InstanceId); /// . This is a named record (not a /// ValueTuple) so it serializes with stable, named JSON properties and can /// evolve additively per REQ-COM-5a. +/// +/// DataSourceReferenceOverride is an optional per-instance override of +/// the OPC UA node id (or other protocol address) for the bound attribute. +/// When non-null it replaces the template's DataSourceReference at +/// flattening time; when null the template default is used. +/// /// -public record ConnectionBinding(string AttributeName, int DataConnectionId); +public record ConnectionBinding( + string AttributeName, + int DataConnectionId, + string? DataSourceReferenceOverride = null); public record SetConnectionBindingsCommand(int InstanceId, IReadOnlyList Bindings); public record SetInstanceOverridesCommand(int InstanceId, IReadOnlyDictionary Overrides); diff --git a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/InstanceService.cs b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/InstanceService.cs index 0dd483a5..1c69a847 100644 --- a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/InstanceService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/InstanceService.cs @@ -330,20 +330,22 @@ public class InstanceService var results = new List(); - foreach (var (attrName, connId) in bindings) + foreach (var b in bindings) { - if (existingMap.TryGetValue(attrName, out var existing)) + if (existingMap.TryGetValue(b.AttributeName, out var existing)) { - existing.DataConnectionId = connId; + existing.DataConnectionId = b.DataConnectionId; + existing.DataSourceReferenceOverride = b.DataSourceReferenceOverride; await _repository.UpdateInstanceConnectionBindingAsync(existing, cancellationToken); results.Add(existing); } else { - var binding = new InstanceConnectionBinding(attrName) + var binding = new InstanceConnectionBinding(b.AttributeName) { InstanceId = instanceId, - DataConnectionId = connId + DataConnectionId = b.DataConnectionId, + DataSourceReferenceOverride = b.DataSourceReferenceOverride }; await _repository.AddInstanceConnectionBindingAsync(binding, cancellationToken); results.Add(binding); diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ConnectionBindingSerializationTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ConnectionBindingSerializationTests.cs index 9a7e504c..856aaf5a 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ConnectionBindingSerializationTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ConnectionBindingSerializationTests.cs @@ -51,4 +51,32 @@ public class ConnectionBindingSerializationTests // ConnectionBinding is a record: each element compares by value. Assert.Equal(original.Bindings, deserialized.Bindings); } + + [Fact] + public void Roundtrip_preserves_override_when_set() + { + var original = new ConnectionBinding("Speed", 7, "ns=2;s=Pump1.Speed"); + + var json = JsonSerializer.Serialize(original); + var roundtripped = JsonSerializer.Deserialize(json); + + Assert.NotNull(roundtripped); + Assert.Equal(original, roundtripped); + Assert.Equal("ns=2;s=Pump1.Speed", roundtripped!.DataSourceReferenceOverride); + } + + [Fact] + public void Roundtrip_defaults_override_to_null_when_absent() + { + // Older site builds will not emit the new field — deserialization + // must produce a null override and equal an explicit-null instance. + const string legacyJson = """{"AttributeName":"Speed","DataConnectionId":7}"""; + + var deserialized = JsonSerializer.Deserialize(legacyJson); + + Assert.NotNull(deserialized); + Assert.Equal("Speed", deserialized!.AttributeName); + Assert.Equal(7, deserialized.DataConnectionId); + Assert.Null(deserialized.DataSourceReferenceOverride); + } }