Auto: abcip-3.2 — symbolic vs logical addressing toggle

Closes #236
This commit is contained in:
Joseph Doherty
2026-04-25 22:58:33 -04:00
parent 73ff10b595
commit 0c6a0d6e50
13 changed files with 1033 additions and 17 deletions

View File

@@ -14,6 +14,8 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
{
private readonly Tag _tag;
private readonly int _connectionSize;
private readonly AddressingMode _addressingMode;
private readonly uint? _logicalInstanceId;
public LibplctagTagRuntime(AbCipTagCreateParams p)
{
@@ -38,6 +40,8 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
if (p.ElementCount is int n && n > 0)
_tag.ElementCount = n;
_connectionSize = p.ConnectionSize;
_addressingMode = p.AddressingMode;
_logicalInstanceId = p.LogicalInstanceId;
}
public async Task InitializeAsync(CancellationToken cancellationToken)
@@ -53,6 +57,16 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
// to the wrapper default. Failures (older / patched wrappers without the internal API)
// are intentionally swallowed so the driver keeps initialising.
TrySetConnectionSize(_tag, _connectionSize);
// PR abcip-3.2 — propagate the addressing mode + (when known) the resolved Symbol
// Object instance ID. Same reflection-fallback shape as ConnectionSize: the libplctag
// .NET wrapper (1.5.x) doesn't expose a public knob for instance-ID addressing, so
// we forward the relevant attribute string through NativeTagWrapper.SetAttributeString.
// Logical mode lights up only when the driver has populated LogicalInstanceId via the
// one-time @tags walk; first reads on a Logical device + every Symbolic-mode read take
// the libplctag default ASCII-symbolic path.
if (_addressingMode == AddressingMode.Logical)
TrySetLogicalAddressing(_tag, _logicalInstanceId);
}
public Task ReadAsync(CancellationToken cancellationToken) => _tag.ReadAsync(cancellationToken);
@@ -86,6 +100,47 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
}
}
/// <summary>
/// PR abcip-3.2 — best-effort propagation of CIP logical-segment / instance-ID
/// addressing to libplctag native. Two attributes are forwarded:
/// <list type="bullet">
/// <item><c>use_connected_msg=1</c> — instance-ID addressing only works over a
/// connected CIP session; switch the tag to use Forward Open + Class3 messaging.</item>
/// <item><c>cip_addr=0x6B,N</c> — replace the ASCII Symbol Object lookup with a
/// direct logical segment reference, where <c>N</c> is the resolved instance ID
/// from the driver's one-time <c>@tags</c> walk.</item>
/// </list>
/// Same reflection-via-<c>NativeTagWrapper.SetAttributeString</c> shape as
/// <see cref="TrySetConnectionSize"/> — the 1.5.x .NET wrapper does not expose a
/// public knob, so we degrade gracefully when the internal API is not present.
/// </summary>
private static void TrySetLogicalAddressing(Tag tag, uint? logicalInstanceId)
{
try
{
var wrapperField = typeof(Tag).GetField("_tag", BindingFlags.NonPublic | BindingFlags.Instance);
var wrapper = wrapperField?.GetValue(tag);
if (wrapper is null) return;
var setStr = wrapper.GetType().GetMethod(
"SetAttributeString",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
binder: null,
types: [typeof(string), typeof(string)],
modifiers: null);
if (setStr is null) return;
setStr.Invoke(wrapper, ["use_connected_msg", "1"]);
if (logicalInstanceId is uint id)
setStr.Invoke(wrapper, ["cip_addr", $"0x6B,{id}"]);
}
catch
{
// Wrapper internals not present / shifted — fall back to symbolic addressing on
// the wire. Driver-level logical-mode bookkeeping (the @tags map) is still useful
// because future wrapper releases may expose this attribute publicly + the
// reflection lights up cleanly then.
}
}
public int GetStatus() => (int)_tag.GetStatus();
public object? DecodeValue(AbCipDataType type, int? bitIndex) => DecodeValueAt(type, 0, bitIndex);