fix(driver-twincat): resolve Medium code-review finding (Driver.TwinCAT-011)

Confirm AdsErrorCode values from Beckhoff.TwinCAT.Ads 7.0.172 and rewrite
MapAdsError with 20 explicit cases. Fix critical bug: AdsSymbolVersionChanged
was 0x0702 (DeviceInvalidGroup) but DeviceSymbolVersionInvalid is 1809
(0x0711); correct constant and all comments. Add BadOutOfService for
DeviceNotReady and BadInvalidState for DeviceInvalidState/PLC-in-Config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-22 10:49:38 -04:00
parent f7d6bd12b9
commit 40b28e8820
5 changed files with 72 additions and 44 deletions

View File

@@ -58,7 +58,7 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
public event EventHandler? OnSymbolVersionChanged;
/// <summary>Raise <see cref="OnSymbolVersionChanged"/> when <paramref name="adsError"/> is 0x0702.</summary>
/// <summary>Raise <see cref="OnSymbolVersionChanged"/> when <paramref name="adsError"/> is <c>DeviceSymbolVersionInvalid</c> (1809 / 0x0711).</summary>
private uint MapAndSignal(uint adsError)
{
if (TwinCATStatusMapper.IsSymbolVersionChanged(adsError))

View File

@@ -21,11 +21,11 @@ public interface ITwinCATClient : IDisposable
/// <summary>
/// Raised when the client observes the ADS symbol-version-changed code
/// (<see cref="TwinCATStatusMapper.AdsSymbolVersionChanged"/>) on any read / write /
/// notification — the signal that a PLC program re-download has invalidated every
/// symbol + notification handle. The driver forwards this to
/// <see cref="Core.Abstractions.IRediscoverable.OnRediscoveryNeeded"/> so Core rebuilds
/// the address space subtree (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
/// (<see cref="TwinCATStatusMapper.AdsSymbolVersionChanged"/> — <c>DeviceSymbolVersionInvalid</c>
/// 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 <see cref="Core.Abstractions.IRediscoverable.OnRediscoveryNeeded"/> so Core
/// rebuilds the address space subtree (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
/// </summary>
event EventHandler? OnSymbolVersionChanged;

View File

@@ -516,14 +516,16 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
}
/// <summary>
/// Routes a wire-detected ADS symbol-version-changed (0x0702) to Core as an
/// <see cref="IRediscoverable"/> 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 <see cref="IRediscoverable"/> 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.
/// </summary>
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);

View File

@@ -1,56 +1,82 @@
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
/// <summary>
/// Maps AMS / ADS error codes to OPC UA StatusCodes. ADS error codes are defined in
/// <c>AdsErrorCode</c> from <c>Beckhoff.TwinCAT.Ads</c> — 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
/// <c>AdsErrorCode</c> enum in <c>Beckhoff.TwinCAT.Ads</c> 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).
/// </summary>
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) ----
/// <summary>
/// ADS <c>ADSERR_DEVICE_SYMBOLVERSIONINVALID</c> — error <c>0x0702</c> (1794 decimal).
/// ADS <c>DeviceSymbolVersionInvalid</c> — 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
/// <see cref="Core.Abstractions.IRediscoverable"/> trigger, not a connection error
/// (docs/v2/driver-specs.md §6, Driver.TwinCAT-013).
/// <para>
/// Note: legacy Beckhoff InfoSys documentation sometimes cites this as "0x0702"; that
/// is a transcription error — 0x0702 is <c>DeviceInvalidGroup</c> (1794). The SDK enum
/// value 1809 (0x0711) is authoritative (Driver.TwinCAT-011).
/// </para>
/// </summary>
public const uint AdsSymbolVersionChanged = 0x0702u;
public const uint AdsSymbolVersionChanged = 1809u; // DeviceSymbolVersionInvalid = 0x0711
/// <summary>True when <paramref name="adsError"/> is the symbol-version-changed code.</summary>
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;
/// <summary>
/// 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 <c>AdsErrorCode</c> enum) to an OPC UA
/// StatusCode. 0 = success. Device-layer codes (0x07000x073F) cover the operations a
/// driver actually encounters during normal runtime.
/// </summary>
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,
};
}