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.
102 lines
5.5 KiB
C#
102 lines
5.5 KiB
C#
using libplctag;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// Maps libplctag / CIP General Status codes to OPC UA StatusCodes. Mirrors the shape of
|
|
/// <c>ModbusDriver.MapModbusExceptionToStatus</c> so Admin UI status displays stay
|
|
/// uniform across drivers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Coverage: the CIP general-status values an AB PLC actually returns during normal
|
|
/// driver operation. Full CIP Volume 1 Appendix B lists 50+ codes; the ones here are the
|
|
/// ones that move the driver's status needle:</para>
|
|
/// <list type="bullet">
|
|
/// <item>0x00 success — OPC UA <c>Good (0)</c>.</item>
|
|
/// <item>0x04 path segment error / 0x05 path destination unknown — <c>BadNodeIdUnknown</c>
|
|
/// (tag doesn't exist).</item>
|
|
/// <item>0x06 partial data transfer — <c>GoodMoreData</c> (fragmented read underway).</item>
|
|
/// <item>0x08 service not supported — <c>BadNotSupported</c> (e.g. write on a safety
|
|
/// partition tag from a non-safety task).</item>
|
|
/// <item>0x0A / 0x13 attribute-list error / insufficient data — <c>BadOutOfRange</c>
|
|
/// (type mismatch or truncated buffer).</item>
|
|
/// <item>0x0B already in requested mode — benign, treated as <c>Good</c>.</item>
|
|
/// <item>0x0E attribute not settable — <c>BadNotWritable</c>.</item>
|
|
/// <item>0x10 device state conflict — <c>BadDeviceFailure</c> (program-mode protected
|
|
/// writes during download / test-mode transitions).</item>
|
|
/// <item>0x16 object does not exist — <c>BadNodeIdUnknown</c>.</item>
|
|
/// <item>0x1E embedded service error — unwrap to the extended status when possible.</item>
|
|
/// <item>libplctag.NET <see cref="Status"/> errors — mapped per-member by
|
|
/// <see cref="MapLibplctagStatus(Status)"/>: timeout, not-found, not-allowed, and
|
|
/// out-of-bounds get their specific OPC UA codes; the remaining transport errors
|
|
/// fold into <c>BadCommunicationError</c>.</item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public static class AbCipStatusMapper
|
|
{
|
|
public const uint Good = 0u;
|
|
public const uint GoodMoreData = 0x00A60000u;
|
|
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;
|
|
|
|
/// <summary>Map a CIP general-status byte to an OPC UA StatusCode.</summary>
|
|
/// <param name="status">The CIP general-status byte value.</param>
|
|
/// <returns>The mapped OPC UA StatusCode value.</returns>
|
|
public static uint MapCipGeneralStatus(byte status) => status switch
|
|
{
|
|
0x00 => Good,
|
|
0x04 or 0x05 => BadNodeIdUnknown,
|
|
0x06 => GoodMoreData,
|
|
0x08 => BadNotSupported,
|
|
0x0A or 0x13 => BadOutOfRange,
|
|
0x0B => Good,
|
|
0x0E => BadNotWritable,
|
|
0x10 => BadDeviceFailure,
|
|
0x16 => BadNodeIdUnknown,
|
|
_ => BadInternalError,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Map a libplctag return/status code to an OPC UA StatusCode. The integer passed here
|
|
/// is <c>(int)Tag.GetStatus()</c> — i.e. the underlying value of the libplctag.NET
|
|
/// <see cref="Status"/> enum, NOT a raw native <c>PLCTAG_ERR_*</c> constant. The wrapper
|
|
/// renumbers the native codes into a contiguous enum, so this method switches on the
|
|
/// <see cref="Status"/> members directly to stay correct if the wrapper renumbers again.
|
|
/// <see cref="Status.Ok"/> is success; <see cref="Status.Pending"/> is an in-flight
|
|
/// operation; every other (negative) member is an error.
|
|
/// </summary>
|
|
/// <param name="status">The libplctag status code as an integer.</param>
|
|
/// <returns>The mapped OPC UA StatusCode value.</returns>
|
|
public static uint MapLibplctagStatus(int status) => MapLibplctagStatus((Status)status);
|
|
|
|
/// <summary>
|
|
/// Map a libplctag.NET <see cref="Status"/> enum value to an OPC UA StatusCode. This is
|
|
/// the strongly-typed core of the mapper; the <c>int</c> overload exists only for the
|
|
/// <see cref="IAbCipTagRuntime.GetStatus"/> seam, which returns the boxed-as-int value.
|
|
/// </summary>
|
|
/// <param name="status">The libplctag Status enum value.</param>
|
|
/// <returns>The mapped OPC UA StatusCode value.</returns>
|
|
public static uint MapLibplctagStatus(Status status) => status switch
|
|
{
|
|
Status.Ok => Good,
|
|
Status.Pending => GoodMoreData,
|
|
Status.ErrorTimeout => BadTimeout,
|
|
Status.ErrorNotFound or Status.ErrorNoMatch or Status.ErrorBadDevice => BadNodeIdUnknown,
|
|
Status.ErrorNotAllowed => BadNotWritable,
|
|
Status.ErrorOutOfBounds or Status.ErrorTooLarge or Status.ErrorTooSmall => BadOutOfRange,
|
|
Status.ErrorUnsupported or Status.ErrorNotImplemented => BadNotSupported,
|
|
Status.ErrorBadConnection or Status.ErrorBadGateway or Status.ErrorBadReply
|
|
or Status.ErrorWinsock or Status.ErrorOpen or Status.ErrorClose
|
|
or Status.ErrorRead or Status.ErrorWrite or Status.ErrorRemoteErr
|
|
or Status.ErrorPartial or Status.ErrorAbort => BadCommunicationError,
|
|
_ => BadCommunicationError,
|
|
};
|
|
}
|