merge: libplctag empty-path fix (null Path for an empty CIP route)

This commit is contained in:
Joseph Doherty
2026-10-07 19:06:36 -04:00
9 changed files with 135 additions and 9 deletions
@@ -0,0 +1,13 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
/// <summary>
/// Normalizes a parsed CIP route for libplctag's <c>Path</c> attribute. libplctag rejects an
/// empty-string path with <c>ErrorBadData</c>; "no route" (Micro800, unrouted MicroLogix) must be
/// expressed by omitting the attribute, i.e. <see langword="null"/>.
/// </summary>
internal static class LibplctagPath
{
/// <summary>Returns <see langword="null"/> for a null, empty or whitespace path; otherwise the path unchanged.</summary>
/// <param name="path">The parsed CIP path.</param>
public static string? Normalize(string? path) => string.IsNullOrWhiteSpace(path) ? null : path;
}
@@ -37,7 +37,7 @@ internal static class LibplctagListingReader
internal static Tag BuildSymbolListTag(AbCipTagCreateParams deviceParams, string listingName = "@tags") => new()
{
Gateway = deviceParams.LibplctagGateway,
Path = deviceParams.CipPath,
Path = LibplctagPath.Normalize(deviceParams.CipPath),
PlcType = MapPlcType(deviceParams.LibplctagPlcAttribute),
Protocol = Protocol.ab_eip,
Name = listingName,
@@ -18,12 +18,18 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
/// <param name="p">The tag creation parameters.</param>
public LibplctagTagRuntime(AbCipTagCreateParams p)
{
_tag = new Tag
_tag = BuildTag(p);
}
/// <summary>Builds the (uninitialized) libplctag tag for <paramref name="p"/>.</summary>
/// <param name="p">The tag creation parameters.</param>
internal static Tag BuildTag(AbCipTagCreateParams p) =>
new Tag
{
// host:port — see AbLegacyTagCreateParams/AbCipTagCreateParams.LibplctagGateway.
// Passing p.Gateway alone drops the authored port onto libplctag's 44818 default (#534).
Gateway = p.LibplctagGateway,
Path = p.CipPath,
Path = LibplctagPath.Normalize(p.CipPath),
PlcType = MapPlcType(p.LibplctagPlcAttribute),
Protocol = Protocol.ab_eip,
Name = p.TagName,
@@ -33,7 +39,6 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
// count so libplctag pulls every element in one CIP read; scalar tags pass 1.
ElementCount = p.ElementCount > 1 ? p.ElementCount : 1,
};
}
/// <inheritdoc />
public Task InitializeAsync(CancellationToken cancellationToken) =>
@@ -39,7 +39,7 @@ internal sealed class LibplctagTemplateReader : IAbCipTemplateReader
internal static Tag BuildTemplateTag(AbCipTagCreateParams deviceParams, uint templateInstanceId) => new()
{
Gateway = deviceParams.LibplctagGateway,
Path = deviceParams.CipPath,
Path = LibplctagPath.Normalize(deviceParams.CipPath),
PlcType = MapPlcType(deviceParams.LibplctagPlcAttribute),
Protocol = Protocol.ab_eip,
Name = $"@udt/{templateInstanceId}",
@@ -17,19 +17,24 @@ internal sealed class LibplctagLegacyTagRuntime : IAbLegacyTagRuntime
/// <param name="p">The parameters for tag creation.</param>
public LibplctagLegacyTagRuntime(AbLegacyTagCreateParams p)
{
_tag = new Tag
_tag = BuildTag(p);
}
/// <summary>Builds the (uninitialized) libplctag tag for <paramref name="p"/>.</summary>
/// <param name="p">The tag creation parameters.</param>
internal static Tag BuildTag(AbLegacyTagCreateParams p) =>
new Tag
{
// host:port — see AbLegacyTagCreateParams/AbCipTagCreateParams.LibplctagGateway.
// Passing p.Gateway alone drops the authored port onto libplctag's 44818 default (#534).
Gateway = p.LibplctagGateway,
Path = p.CipPath,
Path = LibplctagPath.Normalize(p.CipPath),
PlcType = MapPlcType(p.LibplctagPlcAttribute),
Protocol = Protocol.ab_eip, // PCCC-over-EIP; libplctag routes via the PlcType-specific PCCC layer
Name = p.TagName,
Timeout = p.Timeout,
ElementCount = p.ElementCount > 1 ? p.ElementCount : null,
};
}
/// <inheritdoc />
public Task InitializeAsync(CancellationToken cancellationToken) =>
@@ -0,0 +1,13 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
/// <summary>
/// Normalizes a parsed CIP route for libplctag's <c>Path</c> attribute. libplctag rejects an
/// empty-string path with <c>ErrorBadData</c>; "no route" (Micro800, unrouted MicroLogix) must be
/// expressed by omitting the attribute, i.e. <see langword="null"/>.
/// </summary>
internal static class LibplctagPath
{
/// <summary>Returns <see langword="null"/> for a null, empty or whitespace path; otherwise the path unchanged.</summary>
/// <param name="path">The parsed CIP path.</param>
public static string? Normalize(string? path) => string.IsNullOrWhiteSpace(path) ? null : path;
}