Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipStatusMapper.cs
T
Joseph Doherty f087f01d2b fix(abcip): classify every controller refusal as per-tag Bad, keep the runtime
H1/L4: one classification (AbCipRefusals.IsDeviceRefusal) of the statuses
that prove the controller answered - NotFound, OutOfBounds, NotAllowed,
TooLarge, TooSmall, TypeMismatch, Unsupported - shared by the data path, the
Test-Connect probe (IsReachableStatus widened) and the native runtime.

- A refused read/write (thrown or as a status word) is per-tag Bad with a
  once-per-tag Warning, keeps the runtime (no evict, so no lease/Forward-Open
  churn for a sole lease holder), writes no Degraded and, since the device
  answered, advances LastSuccessfulRead without touching the state.
- A refused cold create records the backoff as reachable, so siblings are no
  longer throttled for up to 30 s by one Arr[999].
- The native runtime completes InitializeAsync on any refusal (it used to on
  TypeMismatch/Unsupported only) and reports it on every later read.
- A refused READ of NotAllowed reads BadNotReadable, not BadNotWritable.
- Test Connect names the refusal ("probe tag refused: OutOfBounds").

0x1C/0x20/0xFF-0x2104 still fold into BadReply inside Driver.Ab.Transport
(Lane K follow-up); they remain comms faults until mapped there.

Refs #737
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 14:07:50 -04:00

66 lines
3.4 KiB
C#

using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
/// <summary>
/// Maps transport <see cref="AbStatus"/> values to OPC UA StatusCodes through the table shared with AbLegacy
/// (<see cref="AbOpcUaStatusMap"/>): timeout, not-found, not-allowed, out-of-bounds, unsupported, type-mismatch
/// and device-state-conflict get their specific codes; the remaining transport errors fold into
/// <c>BadCommunicationError</c>. (The raw CIP general-status mapper that used to live here had no callers — the
/// transports surface an <see cref="AbStatus"/>, not the status byte — and was removed.)
/// </summary>
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 BadNotReadable = 0x803A0000u;
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 BadConfigurationError = 0x80890000u;
/// <summary>
/// Map a transport status code to an OPC UA StatusCode. The integer is
/// <see cref="IAbCipTagRuntime.GetStatus"/>, i.e. the underlying value of <see cref="AbStatus"/>.
/// </summary>
/// <param name="status">The <see cref="AbStatus"/> as an integer.</param>
/// <returns>The mapped OPC UA StatusCode value.</returns>
public static uint MapStatus(int status) => MapStatus((AbStatus)status);
/// <summary>
/// Map an <see cref="AbStatus"/> to an OPC UA StatusCode. Timeout, not-found, not-allowed and
/// out-of-bounds get their specific codes; the remaining transport errors fold into
/// <see cref="BadCommunicationError"/>.
/// </summary>
/// <param name="status">The transport status.</param>
/// <returns>The mapped OPC UA StatusCode value.</returns>
public static uint MapStatus(AbStatus status) => AbOpcUaStatusMap.MapStatus(status);
/// <summary>
/// Status for an exception thrown while creating / reading a tag: a transport exception maps
/// through its <see cref="AbStatus"/> (so a tag the controller does not have reads
/// <see cref="BadNodeIdUnknown"/>, not a comms error); anything else is
/// <see cref="BadCommunicationError"/>.
/// </summary>
/// <param name="ex">The exception.</param>
/// <returns>The OPC UA status code.</returns>
public static uint MapException(Exception ex) =>
AbOpcUaStatusMap.MapException(ex);
/// <summary>
/// Status for a READ the controller refused: as <see cref="MapStatus(AbStatus)"/>, except that a
/// <see cref="AbStatus.NotAllowed"/> answer to a read is <see cref="BadNotReadable"/> (the shared table's
/// <c>BadNotWritable</c> describes a refused write).
/// </summary>
/// <param name="status">The refusal status.</param>
/// <returns>The OPC UA status code.</returns>
public static uint MapReadStatus(AbStatus status) =>
status == AbStatus.NotAllowed ? BadNotReadable : MapStatus(status);
}