a1f20e78f9
A deterministic PLC refusal (object does not exist, address out of range,
type inconsistent...) degraded driver health, and an all-refused driver (a
wrong DB number) sat Degraded with LastSuccessfulRead frozen, so the 90 s
comms-loss watchdog reconnected it forever against a reachable PLC.
- IsRefusal / IsAccessDenied classify over the whole exception chain. Live
probe on the snap7 fixture: S7.Net's READ paths raise the item error as a
BARE Exception ("Received error from PLC: ..."), its write paths as
PlcException(WriteData). The old PlcException-only match missed the read
shape entirely, so a real read access-denied was BadCommunicationError +
Degraded instead of the Faulted PUT/GET alert; the Test Connect probe had
the same blind spot.
- A refusal (read or write) is BadDeviceFailure with a once-per-tag Warning,
no health write and no reopen; it counts as answered, so it advances
LastSuccessfulRead and restores Healthy after a comms fault. An undecodable
buffer (S7ValueRangeException) also counts as answered.
- A write batch the CPU answered without a failure advances
LastSuccessfulRead and retires a Degraded the write side itself published.
- Tests that used ReadData as their generic "device fault" now use SendData
so their Degraded leg is still exercised.
Refs #742
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
33 lines
1.1 KiB
C#
33 lines
1.1 KiB
C#
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
|
|
|
|
/// <summary>An <see cref="ILogger{T}"/> that records (level, message) pairs.</summary>
|
|
internal sealed class CapturingS7Logger : ILogger<S7Driver>
|
|
{
|
|
private readonly List<(LogLevel Level, string Message)> _entries = [];
|
|
|
|
/// <summary>A snapshot of every entry logged so far.</summary>
|
|
public IReadOnlyList<(LogLevel Level, string Message)> Entries
|
|
{
|
|
get { lock (_entries) return [.. _entries]; }
|
|
}
|
|
|
|
/// <summary>The Warning messages logged so far.</summary>
|
|
public IReadOnlyList<string> Warnings
|
|
=> [.. Entries.Where(e => e.Level == LogLevel.Warning).Select(e => e.Message)];
|
|
|
|
/// <inheritdoc />
|
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
|
|
|
/// <inheritdoc />
|
|
public bool IsEnabled(LogLevel logLevel) => true;
|
|
|
|
/// <inheritdoc />
|
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
|
Func<TState, Exception?, string> formatter)
|
|
{
|
|
lock (_entries) _entries.Add((logLevel, formatter(state, exception)));
|
|
}
|
|
}
|