diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagStatusTranslation.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagStatusTranslation.cs
index 3d695bdd..059dbc87 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagStatusTranslation.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagStatusTranslation.cs
@@ -37,6 +37,32 @@ internal static class LibplctagStatusTranslation
? ToAbStatus(s)
: AbStatus.BadConnection;
+ ///
+ /// True when is libplctag refusing the tag's own attributes (ErrorBadParam:
+ /// the name or a create attribute does not parse), as opposed to a transport failure.
+ ///
+ /// The exception.
+ public static bool IsTagParameterRefusal(LibPlcTagException ex) =>
+ Enum.TryParse((ex.Message ?? string.Empty).Trim(), ignoreCase: true, out var s) && s == Status.ErrorBadParam;
+
+ ///
+ /// Runs a tag's initialisation: ErrorBadParam there means libplctag rejected the tag's own
+ /// name before any I/O, so it is raised as (per-tag
+ /// BadConfigurationError, no backoff, no health signal — Gitea #570/#593) instead of falling through
+ /// to the generic default that backs the whole device off. Only the
+ /// create phase: a BadParam from a read or write on a live handle is not a name problem.
+ ///
+ /// The libplctag initialisation.
+ /// A task completing when the initialisation does.
+ public static async Task WrapInitialize(Func op)
+ {
+ try { await op().ConfigureAwait(false); }
+ catch (LibPlcTagException ex) when (IsTagParameterRefusal(ex))
+ {
+ throw new AbCipTagConfigurationException($"libplctag rejected the tag's name or attributes: {ex.Message}", ex);
+ }
+ }
+
/// Runs , rethrowing any as .
/// The libplctag operation.
/// A task completing when the operation does.
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs
index a12885ff..1b4d051e 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/LibplctagTagRuntime.cs
@@ -52,7 +52,7 @@ internal sealed class LibplctagTagRuntime : IAbCipTagRuntime
///
public Task InitializeAsync(CancellationToken cancellationToken) =>
- RunReleasingOrphanAsync(() => _tag.InitializeAsync(cancellationToken));
+ RunReleasingOrphanAsync(() => LibplctagStatusTranslation.WrapInitialize(() => _tag.InitializeAsync(cancellationToken)));
///
/// libplctag initialises an uninitialised tag on its first read, so this path can orphan one too.
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/LibplctagStatusTranslationTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/LibplctagStatusTranslationTests.cs
index 39bc958a..fba8f7ea 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/LibplctagStatusTranslationTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/LibplctagStatusTranslationTests.cs
@@ -111,4 +111,41 @@ public sealed class LibplctagStatusTranslationTests
ex.Status.ShouldBe(AbStatus.Timeout);
ex.InnerException.ShouldBeSameAs(inner);
}
+
+ ///
+ /// #593 / #570: ErrorBadParam during initialisation is libplctag refusing the tag's own name — a
+ /// per-tag configuration error (the driver's no-backoff, no-health path), not a device connect failure.
+ ///
+ [Fact]
+ public async Task WrapInitialize_maps_ErrorBadParam_to_a_tag_configuration_exception()
+ {
+ var thrown = await Should.ThrowAsync(() =>
+ LibplctagStatusTranslation.WrapInitialize(() => throw new LibPlcTagException(Status.ErrorBadParam)));
+ thrown.InnerException.ShouldBeOfType();
+ }
+
+ ///
+ /// Falsification pair for the above: every OTHER initialisation failure is left alone (it still reaches
+ /// Wrap and becomes a transport status that opens the backoff window).
+ ///
+ /// The libplctag status.
+ [Theory]
+ [InlineData(Status.ErrorTimeout)]
+ [InlineData(Status.ErrorBadGateway)]
+ [InlineData(Status.ErrorNotFound)]
+ public async Task WrapInitialize_leaves_other_statuses_untouched(Status status)
+ {
+ var thrown = await Should.ThrowAsync(() =>
+ LibplctagStatusTranslation.WrapInitialize(() => throw new LibPlcTagException(status)));
+ thrown.ShouldNotBeOfType();
+ }
+
+ /// A BadParam on the read/write path (through Wrap) is not reclassified as configuration.
+ [Fact]
+ public async Task Wrap_still_maps_ErrorBadParam_to_a_transport_status()
+ {
+ var thrown = await Should.ThrowAsync(() =>
+ LibplctagStatusTranslation.Wrap(() => throw new LibPlcTagException(Status.ErrorBadParam)));
+ thrown.Status.ShouldBe(AbStatus.BadConnection);
+ }
}