diff --git a/code-reviews/Driver.TwinCAT/findings.md b/code-reviews/Driver.TwinCAT/findings.md
index 3d4beee..0402477 100644
--- a/code-reviews/Driver.TwinCAT/findings.md
+++ b/code-reviews/Driver.TwinCAT/findings.md
@@ -298,7 +298,7 @@ symbol-version-changed is never routed to rediscovery (see Driver.TwinCAT-013).
explicit case for symbol-version-changed routed to rediscovery, and for PLC-in-Config mapped
to `BadOutOfService`/`BadInvalidState`.
-**Resolution:** _(open)_
+**Resolution:** Resolved 2026-05-22 — confirmed all codes from `Beckhoff.TwinCAT.Ads` 7.0.172 `AdsErrorCode` enum. Rewrote `MapAdsError` with 20 explicit cases keyed to the correct decimal values. Fixed the critical bug: `AdsSymbolVersionChanged` was `0x0702u` (= `DeviceInvalidGroup`) but the actual `DeviceSymbolVersionInvalid` is 1809 (0x0711); corrected constant and updated all comments. Added `BadOutOfService` for `DeviceNotReady` (PLC not running) and `BadInvalidState` for `DeviceInvalidState` (PLC in Config mode, 0x0712) and `DeviceSymbolVersionInvalid` (0x0711). Added `BadOutOfService`/`BadInvalidState` OPC UA StatusCode constants to the mapper.
### Driver.TwinCAT-012
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs
index af86e4d..6bec502 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/AdsTwinCATClient.cs
@@ -58,7 +58,7 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
public event EventHandler? OnSymbolVersionChanged;
- /// Raise when is 0x0702.
+ /// Raise when is DeviceSymbolVersionInvalid (1809 / 0x0711).
private uint MapAndSignal(uint adsError)
{
if (TwinCATStatusMapper.IsSymbolVersionChanged(adsError))
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/ITwinCATClient.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/ITwinCATClient.cs
index 39b2f78..29339b7 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/ITwinCATClient.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/ITwinCATClient.cs
@@ -21,11 +21,11 @@ public interface ITwinCATClient : IDisposable
///
/// Raised when the client observes the ADS symbol-version-changed code
- /// () on any read / write /
- /// notification — the signal that a PLC program re-download has invalidated every
- /// symbol + notification handle. The driver forwards this to
- /// so Core rebuilds
- /// the address space subtree (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
+ /// ( — DeviceSymbolVersionInvalid
+ /// 1809 / 0x0711) on any read / write / notification — the signal that a PLC program
+ /// re-download has invalidated every symbol + notification handle. The driver forwards
+ /// this to so Core
+ /// rebuilds the address space subtree (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
///
event EventHandler? OnSymbolVersionChanged;
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs
index 2236bfc..af0400f 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATDriver.cs
@@ -516,14 +516,16 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
}
///
- /// Routes a wire-detected ADS symbol-version-changed (0x0702) to Core as an
- /// invocation (Driver.TwinCAT-013). A PLC re-download
- /// invalidates every symbol + notification handle, so the address space must be rebuilt
- /// — this is the documented TwinCAT failure mode, not a transient connection error.
+ /// Routes a wire-detected ADS symbol-version-changed (DeviceSymbolVersionInvalid 1809 /
+ /// 0x0711) to Core as an invocation (Driver.TwinCAT-013).
+ /// A PLC re-download invalidates every symbol + notification handle, so the address
+ /// space must be rebuilt — this is the documented TwinCAT failure mode, not a transient
+ /// connection error.
///
private void HandleSymbolVersionChanged(object? sender, EventArgs e) =>
OnRediscoveryNeeded?.Invoke(this, new RediscoveryEventArgs(
- "TwinCAT symbol-version-changed 0x0702 — PLC program re-downloaded", ScopeHint: "TwinCAT"));
+ "TwinCAT symbol-version-changed (DeviceSymbolVersionInvalid 0x0711) — PLC program re-downloaded",
+ ScopeHint: "TwinCAT"));
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
public async ValueTask DisposeAsync() => await ShutdownAsync(CancellationToken.None).ConfigureAwait(false);
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATStatusMapper.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATStatusMapper.cs
index 78a9b5a..22c4749 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATStatusMapper.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATStatusMapper.cs
@@ -1,56 +1,82 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
///
-/// Maps AMS / ADS error codes to OPC UA StatusCodes. ADS error codes are defined in
-/// AdsErrorCode from Beckhoff.TwinCAT.Ads — this mapper covers the ones a
-/// driver actually encounters during normal operation (symbol-not-found, access-denied,
-/// timeout, router-not-initialized, invalid-group/offset, etc.).
+/// Maps AMS / ADS error codes to OPC UA StatusCodes. ADS error codes are taken from the
+/// AdsErrorCode enum in Beckhoff.TwinCAT.Ads 7.x — numeric values are the
+/// authoritative source (not the hex shorthand in older Beckhoff InfoSys pages, which has
+/// known transcription errors). Key device-layer codes start at 0x0700 (1792 decimal).
///
public static class TwinCATStatusMapper
{
public const uint Good = 0u;
+ // ---- OPC UA StatusCode constants ----
+ public const uint BadInternalError = 0x80020000u;
+ public const uint BadNodeIdUnknown = 0x80340000u;
+ public const uint BadNotWritable = 0x803B0000u;
+ public const uint BadOutOfRange = 0x803C0000u;
+ public const uint BadNotSupported = 0x803D0000u;
+ public const uint BadDeviceFailure = 0x80550000u;
+ public const uint BadCommunicationError = 0x80050000u;
+ public const uint BadTimeout = 0x800A0000u;
+ public const uint BadTypeMismatch = 0x80730000u;
+ public const uint BadOutOfService = 0x80BE0000u;
+ public const uint BadInvalidState = 0x80350000u;
+
+ // ---- AdsErrorCode numeric values (confirmed from Beckhoff.TwinCAT.Ads 7.0.172) ----
+
///
- /// ADS ADSERR_DEVICE_SYMBOLVERSIONINVALID — error 0x0702 (1794 decimal).
+ /// ADS DeviceSymbolVersionInvalid — error code 1809 (0x0711 decimal).
/// Raised by the runtime after a PLC program re-download: every symbol handle and
/// notification handle the driver holds is now stale. The driver treats this as an
/// trigger, not a connection error
/// (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
+ ///
+ /// Note: legacy Beckhoff InfoSys documentation sometimes cites this as "0x0702"; that
+ /// is a transcription error — 0x0702 is DeviceInvalidGroup (1794). The SDK enum
+ /// value 1809 (0x0711) is authoritative (Driver.TwinCAT-011).
+ ///
///
- public const uint AdsSymbolVersionChanged = 0x0702u;
+ public const uint AdsSymbolVersionChanged = 1809u; // DeviceSymbolVersionInvalid = 0x0711
/// True when is the symbol-version-changed code.
public static bool IsSymbolVersionChanged(uint adsError) => adsError == AdsSymbolVersionChanged;
- public const uint BadInternalError = 0x80020000u;
- public const uint BadNodeIdUnknown = 0x80340000u;
- public const uint BadNotWritable = 0x803B0000u;
- public const uint BadOutOfRange = 0x803C0000u;
- public const uint BadNotSupported = 0x803D0000u;
- public const uint BadDeviceFailure = 0x80550000u;
- public const uint BadCommunicationError = 0x80050000u;
- public const uint BadTimeout = 0x800A0000u;
- public const uint BadTypeMismatch = 0x80730000u;
-
///
- /// Map an AMS / ADS error code (uint from AdsErrorCode enum). 0 = success; non-zero
- /// codes follow Beckhoff's AMS error table (7 = target port not found, 1792 =
- /// ADSERR_DEVICE_SRVNOTSUPP, 1793 = ADSERR_DEVICE_INVALIDGRP, 1794 =
- /// ADSERR_DEVICE_INVALIDOFFSET, 1798 = ADSERR_DEVICE_SYMBOLNOTFOUND, 1808 =
- /// ADSERR_DEVICE_ACCESSDENIED, 1861 = ADSERR_CLIENT_SYNCTIMEOUT).
+ /// Map an AMS / ADS error code (uint cast from AdsErrorCode enum) to an OPC UA
+ /// StatusCode. 0 = success. Device-layer codes (0x0700–0x073F) cover the operations a
+ /// driver actually encounters during normal runtime.
///
public static uint MapAdsError(uint adsError) => adsError switch
{
- 0 => Good,
- 6 or 7 => BadCommunicationError, // target port unreachable
- 1792 => BadNotSupported, // service not supported
- 1793 => BadOutOfRange, // invalid index group
- 1794 => BadOutOfRange, // invalid index offset
- 1798 => BadNodeIdUnknown, // symbol not found
- 1807 => BadDeviceFailure, // device in invalid state
- 1808 => BadNotWritable, // access denied
- 1811 or 1812 => BadOutOfRange, // size mismatch
- 1861 => BadTimeout, // sync timeout
- _ => BadCommunicationError,
+ 0 => Good,
+
+ // AMS router / transport errors
+ 6 or 7 => BadCommunicationError, // TargetPortNotFound / TargetMachineNotFound
+ 1285 or 1290 => BadCommunicationError, // RouterNotInitialized / RouterNotActive
+
+ // Device-layer codes (Beckhoff.TwinCAT.Ads AdsErrorCode enum, confirmed 7.0.172)
+ 1792 => BadDeviceFailure, // DeviceError (generic device error)
+ 1793 => BadNotSupported, // DeviceServiceNotSupported
+ 1794 => BadOutOfRange, // DeviceInvalidGroup (ADS index-group error)
+ 1795 => BadOutOfRange, // DeviceInvalidOffset (ADS index-offset error)
+ 1796 => BadNotWritable, // DeviceInvalidAccess (write-access denied)
+ 1797 => BadOutOfRange, // DeviceInvalidSize (size mismatch)
+ 1798 => BadTypeMismatch, // DeviceInvalidData (data format mismatch)
+ 1799 => BadOutOfService, // DeviceNotReady (PLC not running / in config)
+ 1804 => BadNodeIdUnknown, // DeviceNotFound
+ 1807 => BadDeviceFailure, // DeviceIncompatible (0x070E)
+ 1808 => BadNodeIdUnknown, // DeviceSymbolNotFound (0x0710)
+ 1809 => BadInvalidState, // DeviceSymbolVersionInvalid — rediscovery trigger
+ 1810 => BadInvalidState, // DeviceInvalidState (PLC in Config mode, 0x0712)
+ 1811 => BadNotSupported, // DeviceTransModeNotSupported (0x0713)
+ 1812 => BadNodeIdUnknown, // DeviceNotifyHandleInvalid (stale handle, 0x0714)
+ 1827 => BadNotWritable, // DeviceAccessDenied (0x0723)
+ 1844 => BadOutOfRange, // DeviceOutOfRange (0x0734)
+
+ // Client-layer timeout
+ 1861 => BadTimeout, // ClientSyncTimeOut (0x0745)
+
+ _ => BadCommunicationError,
};
}