Merge fix/673-focas-flake: FOCAS LastStatus publication order (#673)
This commit is contained in:
@@ -1232,13 +1232,16 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
|||||||
ProgramInfo: programInfo is not null,
|
ProgramInfo: programInfo is not null,
|
||||||
Timers: timer is not null,
|
Timers: timer is not null,
|
||||||
Status: statusProbe is not null);
|
Status: statusProbe is not null);
|
||||||
state.LastStatus = statusProbe;
|
|
||||||
|
|
||||||
state.FixedTreeCache = new FocasFixedTreeCache(sys, [.. axes], [.. spindles], caps);
|
state.FixedTreeCache = new FocasFixedTreeCache(sys, [.. axes], [.. spindles], caps);
|
||||||
// The bootstrap IS an answer from the device, and Identity comes entirely from it —
|
// The bootstrap IS an answer from the device, and Identity comes entirely from it —
|
||||||
// so stamp liveness here as well as per tick, or a CNC whose axis poll never
|
// so stamp liveness here as well as per tick, or a CNC whose axis poll never
|
||||||
// succeeds would serve a perfectly valid sysinfo as stale.
|
// succeeds would serve a perfectly valid sysinfo as stale.
|
||||||
state.LastFixedPollUtc = DateTime.UtcNow;
|
state.LastFixedPollUtc = DateTime.UtcNow;
|
||||||
|
// Published LAST (release write): LastStatus is the field callers watch for "the first
|
||||||
|
// answer is in", and an authored FIXED:Status/* read is only servable once
|
||||||
|
// FixedTreeCache and LastFixedPollUtc are set (TryReadFixedTreeOnDevice). Publishing it
|
||||||
|
// first let a reader see a status while the read still returned BadCommunicationError (#673).
|
||||||
|
state.LastStatus = statusProbe;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (ct.IsCancellationRequested) { return; }
|
catch (OperationCanceledException) when (ct.IsCancellationRequested) { return; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1998,7 +2001,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ConcurrentDictionary<string, byte> CoalesceRejected { get; } = new(StringComparer.Ordinal);
|
public ConcurrentDictionary<string, byte> CoalesceRejected { get; } = new(StringComparer.Ordinal);
|
||||||
/// <summary>Gets or sets the last <c>cnc_statinfo</c> snapshot (#257); null until the first answer.</summary>
|
/// <summary>Gets or sets the last <c>cnc_statinfo</c> snapshot (#257); null until the first answer.</summary>
|
||||||
public FocasStatus? LastStatus { get; set; }
|
public FocasStatus? LastStatus
|
||||||
|
{
|
||||||
|
get => Volatile.Read(ref _lastStatus);
|
||||||
|
set => Volatile.Write(ref _lastStatus, value);
|
||||||
|
}
|
||||||
|
private FocasStatus? _lastStatus;
|
||||||
/// <summary>Gets or sets the last program information snapshot.</summary>
|
/// <summary>Gets or sets the last program information snapshot.</summary>
|
||||||
public FocasProgramInfo? LastProgramInfo { get; set; }
|
public FocasProgramInfo? LastProgramInfo { get; set; }
|
||||||
/// <summary>Gets or sets the cached first-axis dynamic snapshot — feeds Program/Number, /MainNumber, /Sequence.</summary>
|
/// <summary>Gets or sets the cached first-axis dynamic snapshot — feeds Program/Number, /MainNumber, /Sequence.</summary>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public sealed class FocasStatusFlagsTests
|
|||||||
var client = new FakeFocasClient { Status = new FocasStatus(Auto: 1, Run: 3, Motion: 1, Mstb: 2, Emergency: 0, Alarm: 1, Edit: 0, TmMode: 4) };
|
var client = new FakeFocasClient { Status = new FocasStatus(Auto: 1, Run: 3, Motion: 1, Mstb: 2, Emergency: 0, Alarm: 1, Edit: 0, TmMode: 4) };
|
||||||
var drv = NewDriver(client);
|
var drv = NewDriver(client);
|
||||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
await WaitAsync(() => drv.GetDeviceState(Host)?.LastStatus is not null);
|
await WaitAsync(() => drv.GetDeviceState(Host) is { LastFixedPollUtc: not null, FixedTreeCache: not null, LastStatus: not null });
|
||||||
|
|
||||||
string[] fields = ["Auto", "Run", "Motion", "Mstb", "Emergency", "Alarm", "Edit", "TmMode"];
|
string[] fields = ["Auto", "Run", "Motion", "Mstb", "Emergency", "Alarm", "Edit", "TmMode"];
|
||||||
int[] expected = [1, 3, 1, 2, 0, 1, 0, 4];
|
int[] expected = [1, 3, 1, 2, 0, 1, 0, 4];
|
||||||
@@ -82,7 +82,8 @@ public sealed class FocasStatusFlagsTests
|
|||||||
var drv = NewDriver(client,
|
var drv = NewDriver(client,
|
||||||
new FocasTagDefinition("EStop", Host, "FIXED:Status/Emergency", FocasDataType.Int32, Writable: false));
|
new FocasTagDefinition("EStop", Host, "FIXED:Status/Emergency", FocasDataType.Int32, Writable: false));
|
||||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
await WaitAsync(() => drv.GetDeviceState(Host)?.LastStatus is not null);
|
// Wait for full readiness: an authored FIXED read needs the cache and the poll stamp too (#673).
|
||||||
|
await WaitAsync(() => drv.GetDeviceState(Host) is { LastFixedPollUtc: not null, FixedTreeCache: not null, LastStatus: not null });
|
||||||
|
|
||||||
var snap = (await drv.ReadAsync(["EStop"], CancellationToken.None))[0];
|
var snap = (await drv.ReadAsync(["EStop"], CancellationToken.None))[0];
|
||||||
snap.StatusCode.ShouldBe(FocasStatusMapper.Good);
|
snap.StatusCode.ShouldBe(FocasStatusMapper.Good);
|
||||||
@@ -97,7 +98,7 @@ public sealed class FocasStatusFlagsTests
|
|||||||
var client = new FakeFocasClient { Status = new FocasStatus(1, 0, 0, 0, 0, 0, 0, 0) };
|
var client = new FakeFocasClient { Status = new FocasStatus(1, 0, 0, 0, 0, 0, 0, 0) };
|
||||||
var drv = NewDriver(client);
|
var drv = NewDriver(client);
|
||||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||||
await WaitAsync(() => drv.GetDeviceState(Host)?.LastStatus is not null);
|
await WaitAsync(() => drv.GetDeviceState(Host) is { LastFixedPollUtc: not null, FixedTreeCache: not null, LastStatus: not null });
|
||||||
|
|
||||||
client.Status = new FocasStatus(1, 3, 1, 0, 0, 0, 0, 0);
|
client.Status = new FocasStatus(1, 3, 1, 0, 0, 0, 0, 0);
|
||||||
await WaitAsync(() => drv.GetDeviceState(Host)?.LastStatus?.Run == 3);
|
await WaitAsync(() => drv.GetDeviceState(Host)?.LastStatus?.Run == 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user