b95b99ba8e
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.
87 lines
5.0 KiB
C#
87 lines
5.0 KiB
C#
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,
|
||
};
|
||
}
|