fix(twincat): read-singly fallback for any non-comms overall sum error, once-per-device warning, wording (#645 re-review)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -85,7 +85,7 @@ internal class FakeTwinCATClient : ITwinCATClient
|
||||
/// <summary>Gets or sets an exception <see cref="ReadValuesAsync"/> throws instead of answering — a
|
||||
/// <see cref="TwinCATSumReadRefusedException"/> models a target without sum commands.</summary>
|
||||
public Exception? SumReadException { get; set; }
|
||||
/// <summary>Gets or sets the ADS state text <see cref="ReadAdsStateAsync"/> returns.</summary>
|
||||
/// <summary>Gets or sets the ADS state text <see cref="LastAdsState"/> returns.</summary>
|
||||
public string? AdsStateText { get; set; }
|
||||
|
||||
/// <summary>Simulates an ADS sum-read: records the batch size, then answers each item from the same
|
||||
@@ -99,6 +99,7 @@ internal class FakeTwinCATClient : ITwinCATClient
|
||||
if (ThrowOnRead) throw Exception ?? new InvalidOperationException();
|
||||
SumReadBatches.Add(reads.Count);
|
||||
if (SumReadException is { } ex) throw ex;
|
||||
if (SimulateSumFallback) SumFallbackCount++;
|
||||
var results = new (object? value, uint status)[reads.Count];
|
||||
for (var i = 0; i < reads.Count; i++)
|
||||
{
|
||||
@@ -109,6 +110,12 @@ internal class FakeTwinCATClient : ITwinCATClient
|
||||
return Task.FromResult<IReadOnlyList<(object? value, uint status)>>(results);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets whether each batch counts as a non-latching sum fallback.</summary>
|
||||
public bool SimulateSumFallback { get; set; }
|
||||
/// <summary>Gets the simulated count of non-latching sum fallbacks.</summary>
|
||||
public long SumFallbackCount { get; private set; }
|
||||
/// <summary>Gets the simulated overall code of the last fallback.</summary>
|
||||
public uint LastSumFallbackCode => 1795;
|
||||
/// <summary>Gets the ADS state the "probe" last read.</summary>
|
||||
public string? LastAdsState => AdsStateText;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class TwinCATDiagnosticsTests
|
||||
diag.GetDiagnostics().ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies a single-device driver publishes flat keys with state, app, cycle, exec and jitter.</summary>
|
||||
/// <summary>Verifies a single-device driver publishes flat keys with state, app, cycle, exec and headroom.</summary>
|
||||
[Fact]
|
||||
public async Task Single_device_publishes_flat_keys()
|
||||
{
|
||||
|
||||
@@ -46,10 +46,12 @@ public sealed class TwinCATSumReadPlannerTests
|
||||
[InlineData(1792u, false, true)]
|
||||
[InlineData(1797u, false, true)]
|
||||
[InlineData(1798u, false, true)]
|
||||
[InlineData(1795u, false, false)]
|
||||
[InlineData(6u, false, false)]
|
||||
[InlineData(1795u, false, true)] // invalid offset: this request rejected, read singly
|
||||
[InlineData(1796u, false, true)]
|
||||
[InlineData(1808u, false, true)]
|
||||
[InlineData(6u, false, false)] // comms: every item goes Bad
|
||||
[InlineData(7u, false, false)]
|
||||
[InlineData(1861u, false, false)]
|
||||
[InlineData(1808u, false, false)]
|
||||
public void Overall_ads_codes_classify(uint code, bool refusal, bool rejected)
|
||||
{
|
||||
TwinCATSumReadPlanner.IsRefusal(code).ShouldBe(refusal);
|
||||
|
||||
@@ -156,6 +156,22 @@ public sealed class TwinCATSumReadTests
|
||||
factory.Clients[0].SumReadBatches.ShouldBe([2, 2]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies a non-latching fallback is surfaced once per device, not once per poll.</summary>
|
||||
[Fact]
|
||||
public async Task Non_latching_fallback_warns_once_per_device()
|
||||
{
|
||||
var (drv, factory) = NewDriver(true, 200, Tag("A"), Tag("B"));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = () => new FakeTwinCATClient { Values = { ["MAIN.A"] = 1, ["MAIN.B"] = 2 }, SimulateSumFallback = true };
|
||||
|
||||
await drv.ReadAsync(["A", "B"], CancellationToken.None);
|
||||
var state = drv.GetDeviceState(Host)!;
|
||||
state.SumFallbackWarned.ShouldBeTrue();
|
||||
state.SumReadUnsupported.ShouldBeFalse(); // not latched: the next poll tries the sum command again
|
||||
await drv.ReadAsync(["A", "B"], CancellationToken.None);
|
||||
factory.Clients[0].SumReadBatches.ShouldBe([2, 2]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies unknown references do not disturb result order.</summary>
|
||||
[Fact]
|
||||
public async Task Unknown_reference_keeps_its_slot_in_a_batched_poll()
|
||||
|
||||
Reference in New Issue
Block a user