From 8e96e8d108e5c0a9d76b18f0ad75a2cb0ab1e110 Mon Sep 17 00:00:00 2001 From: Johnny Gorea Date: Sun, 6 Sep 2026 12:29:00 +0800 Subject: [PATCH] libplctag core added the connection_group_id tag attribute (see libplctag/libplctag#312) to allow a process to open multiple independent connections/sessions to the same PLC. This attribute was not yet exposed by the high-level Tag class. This adds a nullable ConnectionGroupId property, following the same GetField/SetField pattern used by the other simple integer attributes (e.g. ElementCount), and wires it into the generated attribute string as connection_group_id. --- docs/libplctag.md | 26 +++++++++++++++++++ examples/CSharp NativeImport/CallbackEx.cs | 2 +- examples/CSharp NativeImport/Multithread.cs | 3 ++- .../NativeImportExample.cs | 7 ++--- src/libplctag.Tests/OtherTests.cs | 18 +++++++++++++ src/libplctag/Tag.cs | 23 ++++++++++++++++ 6 files changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/libplctag.md b/docs/libplctag.md index 20995632..cf490b91 100644 --- a/docs/libplctag.md +++ b/docs/libplctag.md @@ -34,6 +34,32 @@ myTag.Write(); Console.WriteLine($"Updated value: {updatedValue}"); ``` +To force separate channels to the same PLC from one process, set different `ConnectionGroupId` values: + +```csharp +var channel1Tag = new Tag() +{ + Name = "SomeDINT", + Gateway = "10.10.10.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + ConnectionGroupId = 1 +}; + +var channel2Tag = new Tag() +{ + Name = "SomeOtherDINT", + Gateway = "10.10.10.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + ConnectionGroupId = 2 +}; +``` + +If `ConnectionGroupId` is not set, the channel is shared. + See the examples projects for further detail and usage: * [C# (.NET)](../examples/CSharp%20DotNetCore/) diff --git a/examples/CSharp NativeImport/CallbackEx.cs b/examples/CSharp NativeImport/CallbackEx.cs index d1348dee..bc188419 100644 --- a/examples/CSharp NativeImport/CallbackEx.cs +++ b/examples/CSharp NativeImport/CallbackEx.cs @@ -29,7 +29,7 @@ public static void Run() { // Create the tag handle - var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=127.0.0.1&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MyTag[0]", 1000); + var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=127.0.0.1&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MyTag[0]&connection_group_id=1", 1000); var statusBeforeRead = plctag.plc_tag_status(tagHandle); if (statusBeforeRead != 0) { diff --git a/examples/CSharp NativeImport/Multithread.cs b/examples/CSharp NativeImport/Multithread.cs index 10aed3e0..8c8859b7 100644 --- a/examples/CSharp NativeImport/Multithread.cs +++ b/examples/CSharp NativeImport/Multithread.cs @@ -13,7 +13,8 @@ namespace NativeImport_Examples { class Multithread { - const string TAG_PATH = "protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT"; + // Optional: set connection_group_id to isolate this tag's channel from other tags in the same process. + const string TAG_PATH = "protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT&connection_group_id=1"; const int ELEM_COUNT = 1; const int ELEM_SIZE = 4; const int DATA_TIMEOUT = 2000; diff --git a/examples/CSharp NativeImport/NativeImportExample.cs b/examples/CSharp NativeImport/NativeImportExample.cs index e2c6c972..cc4b83aa 100644 --- a/examples/CSharp NativeImport/NativeImportExample.cs +++ b/examples/CSharp NativeImport/NativeImportExample.cs @@ -21,7 +21,8 @@ public static void Run() //Please reference the libplctag documentation for API and usage - var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT", 1000); + // Optional: set connection_group_id to create a separate channel to the same PLC when needed. + var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT&connection_group_id=1", 1000); while (plctag.plc_tag_status(tagHandle) == 1) { @@ -54,7 +55,7 @@ public static void Run() public static void RunCallbackExample() { - var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT", 1000); + var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=192.168.0.10&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MY_DINT&connection_group_id=1", 1000); while (plctag.plc_tag_status(tagHandle) == 1) { @@ -105,7 +106,7 @@ public static void RunLoggerExample() Console.WriteLine($"Something went wrong {statusAfterRegistration}"); } - var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=127.0.0.1&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MyTag&debug=4", 1000); + var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=127.0.0.1&path=1,0&plc=LGX&elem_size=4&elem_count=1&name=MyTag&debug=4&connection_group_id=1", 1000); while (plctag.plc_tag_status(tagHandle) == 1) { diff --git a/src/libplctag.Tests/OtherTests.cs b/src/libplctag.Tests/OtherTests.cs index d1b908c5..3df63729 100644 --- a/src/libplctag.Tests/OtherTests.cs +++ b/src/libplctag.Tests/OtherTests.cs @@ -68,5 +68,23 @@ public void Attribute_string_does_not_contain_unset_properties() nativeTag.Verify(m => m.plc_tag_create_ex(expectedAttributeString, It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } + + [Fact] + public void Attribute_string_contains_connection_group_id_when_set() + { + // Arrange + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object) + { + ConnectionGroupId = 7, + }; + + // Act + tag.Initialize(); + + // Assert + var expectedAttributeString = "connection_group_id=7"; + nativeTag.Verify(m => m.plc_tag_create_ex(expectedAttributeString, It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } } } diff --git a/src/libplctag/Tag.cs b/src/libplctag/Tag.cs index 730b346f..daa63e77 100644 --- a/src/libplctag/Tag.cs +++ b/src/libplctag/Tag.cs @@ -47,6 +47,7 @@ public sealed class Tag : IDisposable private bool? _allowFieldResize; private int? _readCacheMillisecondDuration; private uint? _maxRequestsInFlight; + private int? _connectionGroupId; private TimeSpan _timeout = defaultTimeout; private TimeSpan? _autoSyncReadInterval; private TimeSpan? _autoSyncWriteInterval; @@ -630,6 +631,27 @@ public uint? MaxRequestsInFlight set => SetField(ref _maxRequestsInFlight, value); } + /// + /// [OPTIONAL] + /// A user-defined identifier, in the form of an integer, used to separate PLC connections into distinct groups. + /// + /// + /// + /// By default, tags that share the same and are + /// multiplexed over a single shared connection/session to the PLC. + /// Setting to a distinct value forces those tags onto their + /// own, independent connection, even when the gateway and path are otherwise identical. + /// This allows a single process to maintain multiple simultaneous connections to the same PLC, + /// for example to isolate latency-sensitive polling from bulk/background requests. + /// Like the other connection attributes, this cannot be changed after the tag is initialized. + /// Note: Leave unset to preserve existing connection sharing behavior. + /// + public int? ConnectionGroupId + { + get => GetField(ref _connectionGroupId); + set => SetField(ref _connectionGroupId, value); + } + public void Dispose() { @@ -1207,6 +1229,7 @@ string FormatTimeSpan(TimeSpan? timespan) { "str_pad_bytes", StringPadBytes?.ToString() }, { "str_total_length", StringTotalLength?.ToString() }, { "max_requests_in_flight", MaxRequestsInFlight?.ToString() }, + { "connection_group_id", ConnectionGroupId?.ToString() }, { "allow_field_resize", FormatNullableBoolean(AllowFieldResize) }, };