Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT/TwinCATStatusMapper.cs
T
Joseph Doherty b95b99ba8e fix(drivers): correct 16 wrong OPC UA status-code constants + guard them by reflection (#497)
Every value verified against Opc.Ua.StatusCodes in the pinned SDK (1.5.378.106) by
reflection, not by copying siblings. All are client-visible: OPC UA clients branch on
status.

The three named in #497:
  - Historian.Gateway SampleMapper "BadNoData"  0x800E0000 -> 0x809B0000
    (0x800E0000 is BadServerHalted)
  - Galaxy "BadTimeout"                         0x800B0000 -> 0x800A0000
    (0x800B0000 is BadServiceUnsupported)
  - TwinCAT BadTypeMismatch                     0x80730000 -> 0x80740000
    (0x80730000 is BadWriteNotSupported)

BadTypeMismatch was wrong in three MORE drivers the issue did not name — FOCAS,
AbLegacy and AbCip carried the same 0x80730000. Every call site is a
FormatException/InvalidCastException conversion failure, so the name was right and
the value was wrong in all four.

Adding the reflection guard then surfaced nine further defects offline tests had
never checked:
  - Galaxy GoodLocalOverride  0x00D80000 -> 0x00960000  (not a UA code at all)
  - Galaxy UncertainLastUsableValue        0x40A40000 -> 0x40900000
  - Galaxy UncertainSensorNotAccurate      0x408D0000 -> 0x40930000
  - Galaxy UncertainEngineeringUnitsExceeded 0x408E0000 -> 0x40940000
  - Galaxy UncertainSubNormal              0x408F0000 -> 0x40950000
  - TwinCAT BadOutOfService   0x80BE0000 -> 0x808D0000 (was BadProtocolVersionUnsupported)
  - TwinCAT BadInvalidState   0x80350000 -> 0x80AF0000 (was BadAttributeIdInvalid)
  - AbCip/AbLegacy GoodMoreData 0x00A70000 -> 0x00A60000 (was GoodCommunicationEvent)
  - Historian.Gateway GatewayQualityMapper Good_LocalOverride 0x00D80000 -> 0x00960000

Galaxy's whole Uncertain block read as though the OPC DA quality byte could be
shifted into the UA substatus position; it cannot — the substatuses are an unrelated
enumeration. GatewayQualityMapper already held the correct table, which is what the
Galaxy values are now reconciled against.

Guard: StatusCodeParityTests (Core.Abstractions.Tests, which already project-
references every driver) reflects over every `const uint` in the deployed
ZB.MOM.WW.OtOpcUa.Driver.*.dll whose name reads as a status code, and asserts it
equals Opc.Ua.StatusCodes.<name>. Discovery is by convention, so a new driver
assembly referenced by that project is covered with no edit here. It went red on all
nine defects above before the fixes and now checks 109 constants green.

Because reflection can only see a NAMED constant, two inline literals were hoisted so
the guard can reach them: Galaxy's BadTimeout/Bad into StatusCodeMap, and
GatewayQualityMapper's 15-entry table into a new HistorianStatusCodes. An inline
`0x800B0000u, // BadTimeout` at a call site is exactly how the Galaxy defect survived.

The drivers stay SDK-free by design; only the test project references Opc.Ua.Core,
as the oracle.

Also corrected: tests that pinned the wrong values (Galaxy StatusCodeMapTests,
SampleMapperTests, GatewayQualityMapperTests — all written from the same bad source),
a mislabelled comment in DriverInstanceActorTests, and stale constants in two design
docs, one of them the unexecuted MTConnect driver design that would have propagated
BadNoData's wrong value into a new driver.

The CLI's SnapshotFormatter value->name table was checked against the SDK and is
correct; no change needed.
2026-07-25 15:34:31 -04:00

87 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
/// <summary>
/// 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 = 0x808B0000u;
public const uint BadCommunicationError = 0x80050000u;
public const uint BadTimeout = 0x800A0000u;
public const uint BadTypeMismatch = 0x80740000u;
public const uint BadOutOfService = 0x808D0000u;
public const uint BadInvalidState = 0x80AF0000u;
// ---- AdsErrorCode numeric values (confirmed from Beckhoff.TwinCAT.Ads 7.0.172) ----
/// <summary>
/// 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).
/// <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.
/// </para>
/// </summary>
public const uint AdsSymbolVersionChanged = 1809u; // DeviceSymbolVersionInvalid = 0x0711
/// <summary>True when <paramref name="adsError"/> is the symbol-version-changed code.</summary>
/// <param name="adsError">The ADS error code to check.</param>
/// <returns>True if the error code indicates symbol version changed.</returns>
public static bool IsSymbolVersionChanged(uint adsError) => adsError == AdsSymbolVersionChanged;
/// <summary>
/// Map an AMS / ADS error code (uint cast from <c>AdsErrorCode</c> enum) to an OPC UA
/// StatusCode. 0 = success. Device-layer codes (0x0700–0x073F) cover the operations a
/// driver actually encounters during normal runtime.
/// </summary>
/// <param name="adsError">The ADS error code to map.</param>
/// <returns>The corresponding OPC UA status code.</returns>
public static uint MapAdsError(uint adsError) => adsError switch
{
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,
};
}