diff --git a/WHATS_NEW.md b/WHATS_NEW.md index d197283b5..95c1ab63b 100644 --- a/WHATS_NEW.md +++ b/WHATS_NEW.md @@ -29,6 +29,8 @@ Bug fixes: - When DCS was configured to never terminate on reset, sometimes it failed to establish a connection again - Subscribe connection clients failed to clear `global` dictionary on reset - Fixed `exists` function in conditional G-code +- When retrieving file info, DCS returned the simulated time instead of the slicer time +- Fixed timing issue causing simulation times to be written incorrectly to G-code files Known limitations: - Auto-resume on power loss cannot be configured (M916) diff --git a/src/DuetAPI/ObjectModel/Plugins/PluginManifest.cs b/src/DuetAPI/ObjectModel/Plugins/PluginManifest.cs index 91e9f3206..4bd07826d 100644 --- a/src/DuetAPI/ObjectModel/Plugins/PluginManifest.cs +++ b/src/DuetAPI/ObjectModel/Plugins/PluginManifest.cs @@ -1,6 +1,5 @@ using DuetAPI.Utility; using System; -using System.Text.Json; namespace DuetAPI.ObjectModel { diff --git a/src/DuetControlServer/FileExecution/Job.cs b/src/DuetControlServer/FileExecution/Job.cs index b3e91e558..30e9d8a28 100644 --- a/src/DuetControlServer/FileExecution/Job.cs +++ b/src/DuetControlServer/FileExecution/Job.cs @@ -228,7 +228,7 @@ public static async Task Run() _logger.Info("Starting file print"); // Notify RRF - SPI.Interface.SetPrintStarted(); + SPI.Interface.StartPrint(); // Process the file Queue codes = new(); @@ -369,27 +369,26 @@ public static async Task Run() // Notify RepRapFirmware that the print file has been closed if (IsCancelled) { + await SPI.Interface.StopPrint(PrintStoppedReason.UserCancelled); _logger.Info("Cancelled job file"); - await SPI.Interface.SetPrintStopped(PrintStoppedReason.UserCancelled); } else if (IsAborted) { + await SPI.Interface.StopPrint(PrintStoppedReason.Abort); _logger.Info("Aborted job file"); - await SPI.Interface.SetPrintStopped(PrintStoppedReason.Abort); } else { + await SPI.Interface.StopPrint(PrintStoppedReason.NormalCompletion); _logger.Info("Finished job file"); - await SPI.Interface.SetPrintStopped(PrintStoppedReason.NormalCompletion); } - // Update the object model again + // Update special fields that are not available in RRF using (await Provider.AccessReadWriteAsync()) { Provider.Get.Job.LastFileAborted = IsAborted; Provider.Get.Job.LastFileCancelled = IsCancelled; Provider.Get.Job.LastFileSimulated = IsSimulating; - Provider.Get.Job.LastFileName = Provider.Get.Job.File.FileName; } // Update the last simulated time diff --git a/src/DuetControlServer/Files/InfoParser.cs b/src/DuetControlServer/Files/InfoParser.cs index d9facd2dc..fb778e7d1 100644 --- a/src/DuetControlServer/Files/InfoParser.cs +++ b/src/DuetControlServer/Files/InfoParser.cs @@ -114,11 +114,11 @@ private static async Task ParseHeader(StreamReader reader, ParsedFileInfo partia } else if (!string.IsNullOrWhiteSpace(code.Comment)) { + gotNewInfo |= (partialFileInfo.SimulatedTime == null) && FindSimulatedTime(code.Comment, ref partialFileInfo); + gotNewInfo |= !gotNewInfo && (partialFileInfo.PrintTime == null) && FindPrintTime(code.Comment, ref partialFileInfo); gotNewInfo |= (partialFileInfo.LayerHeight == 0) && FindLayerHeight(code.Comment, ref partialFileInfo); gotNewInfo |= FindFilamentUsed(code.Comment, ref partialFileInfo); gotNewInfo |= string.IsNullOrEmpty(partialFileInfo.GeneratedBy) && FindGeneratedBy(code.Comment, ref partialFileInfo); - gotNewInfo |= (partialFileInfo.PrintTime == null) && FindPrintTime(code.Comment, ref partialFileInfo); - gotNewInfo |= (partialFileInfo.SimulatedTime == null) && FindSimulatedTime(code.Comment, ref partialFileInfo); } // Is the file info complete? @@ -190,11 +190,11 @@ private static async Task ParseFooter(StreamReader reader, ParsedFileInfo partia } else if (!string.IsNullOrWhiteSpace(code.Comment)) { + gotNewInfo |= (partialFileInfo.SimulatedTime == null) && FindSimulatedTime(code.Comment, ref partialFileInfo); + gotNewInfo |= !gotNewInfo && (partialFileInfo.PrintTime == null) && FindPrintTime(code.Comment, ref partialFileInfo); gotNewInfo |= (partialFileInfo.LayerHeight == 0) && FindLayerHeight(code.Comment, ref partialFileInfo); gotNewInfo |= !hadFilament && FindFilamentUsed(code.Comment, ref partialFileInfo); gotNewInfo |= string.IsNullOrEmpty(partialFileInfo.GeneratedBy) && FindGeneratedBy(code.Comment, ref partialFileInfo); - gotNewInfo |= (partialFileInfo.PrintTime == null) && FindPrintTime(code.Comment, ref partialFileInfo); - gotNewInfo |= (partialFileInfo.SimulatedTime == null) && FindSimulatedTime(code.Comment, ref partialFileInfo); } // Prepare to read the next code diff --git a/src/DuetControlServer/Model/Updater.cs b/src/DuetControlServer/Model/Updater.cs index df3254897..d9322c61b 100644 --- a/src/DuetControlServer/Model/Updater.cs +++ b/src/DuetControlServer/Model/Updater.cs @@ -24,7 +24,22 @@ public static class Updater /// /// General-purpose lock for this class /// - private static readonly AsyncMonitor _monitor = new(); + private static readonly AsyncLock _lock = new(); + + /// + /// First condition variable for object model updates + /// + private static readonly AsyncConditionVariable _updateConditionA = new(_lock); + + /// + /// First condition variable for object model updates + /// + private static readonly AsyncConditionVariable _updateConditionB = new(_lock); + + /// + /// Whether a client waiting for an object model update shall use A or B + /// + private static bool _useUpdateConditionA; /// /// Dictionary of main keys vs last sequence numbers @@ -38,9 +53,9 @@ public static class Updater /// Asynchronous task public static async Task WaitForFullUpdate(CancellationToken cancellationToken) { - using (await _monitor.EnterAsync(cancellationToken)) + using (await _lock.LockAsync(cancellationToken)) { - await _monitor.WaitAsync(cancellationToken); + await (_useUpdateConditionA ? _updateConditionA : _updateConditionB).WaitAsync(cancellationToken); Program.CancellationToken.ThrowIfCancellationRequested(); } } @@ -51,9 +66,10 @@ public static async Task WaitForFullUpdate(CancellationToken cancellationToken) /// Asynchronous task public static async Task MachineModelFullyUpdated() { - using (await _monitor.EnterAsync(Program.CancellationToken)) + using (await _lock.LockAsync(Program.CancellationToken)) { - _monitor.PulseAll(); + _useUpdateConditionA = !_useUpdateConditionA; + (_useUpdateConditionA ? _updateConditionA : _updateConditionB).NotifyAll(); } } @@ -65,7 +81,7 @@ public static async Task MachineModelFullyUpdated() public static async Task ProcessLegacyConfigResponse(byte[] response) { using JsonDocument jsonDocument = JsonDocument.Parse(response); - using (await _monitor.EnterAsync(Program.CancellationToken)) + using (await _lock.LockAsync(Program.CancellationToken)) { if (jsonDocument.RootElement.TryGetProperty("boardName", out JsonElement boardName)) { @@ -94,7 +110,8 @@ public static async Task ProcessLegacyConfigResponse(byte[] response) } // Cannot perform any further updates... - _monitor.PulseAll(); + _useUpdateConditionA = !_useUpdateConditionA; + (_useUpdateConditionA ? _updateConditionA : _updateConditionB).NotifyAll(); // Check if the firmware is supposed to be updated if (Settings.UpdateOnly && !_updatingFirmware) @@ -123,7 +140,7 @@ public static async Task Run() try { // Request the limits if no sequence numbers have been set yet - using (await _monitor.EnterAsync(Program.CancellationToken)) + using (await _lock.LockAsync(Program.CancellationToken)) { if (_lastSeqs.IsEmpty) { @@ -223,10 +240,8 @@ public static async Task Run() } // Object model is now up-to-date, notify waiting clients - using (await _monitor.EnterAsync(Program.CancellationToken)) - { - _monitor.PulseAll(); - } + _useUpdateConditionA = !_useUpdateConditionA; + (_useUpdateConditionA ? _updateConditionA : _updateConditionB).NotifyAll(); // Check if the firmware is supposed to be updated if (Settings.UpdateOnly && !_updatingFirmware) diff --git a/src/DuetControlServer/SPI/Interface.cs b/src/DuetControlServer/SPI/Interface.cs index a7fbbc09a..cb7077e11 100644 --- a/src/DuetControlServer/SPI/Interface.cs +++ b/src/DuetControlServer/SPI/Interface.cs @@ -80,11 +80,15 @@ private sealed class PendingModelQuery private static TaskCompletionSource _firmwareHaltRequest; private static TaskCompletionSource _firmwareResetRequest; + // Print handling + private static volatile bool _startPrint; + private static readonly AsyncLock _stopPrintLock = new(); + private static PrintStoppedReason _stopPrintReason; + private static TaskCompletionSource _stopPrintRequest; + // Miscellaneous requests + private static volatile bool _assignFilaments; private static readonly Dictionary _extruderFilamentMapping = new(); - private static readonly AsyncLock _printStopppedReasonLock = new(); - private static PrintStoppedReason? _printStoppedReason; - private static volatile bool _printStarted, _assignFilaments; private static readonly Queue> _messagesToSend = new(); // Partial incoming message (if any) @@ -418,33 +422,40 @@ public static async Task ResetFirmware() /// Notify the firmware that a file print has started /// /// Not connected over SPI - public static void SetPrintStarted() + public static void StartPrint() { if (Settings.NoSpi) { throw new InvalidOperationException("Not connected over SPI"); } - _printStarted = true; + _startPrint = true; } /// /// Notify the firmware that the file print has been stopped /// - /// Reason why the print has stopped + /// Reason why the print has stopped /// Asynchronous task /// Not connected over SPI - public static async Task SetPrintStopped(PrintStoppedReason stopReason) + public static async Task StopPrint(PrintStoppedReason reason) { if (Settings.NoSpi) { throw new InvalidOperationException("Not connected over SPI"); } - using (await _printStopppedReasonLock.LockAsync(Program.CancellationToken)) + Task onPrintStopped; + using (await _stopPrintLock.LockAsync(Program.CancellationToken)) { - _printStoppedReason = stopReason; + _stopPrintReason = reason; + if (_stopPrintRequest == null) + { + _stopPrintRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + onPrintStopped = _stopPrintRequest.Task; } + await onPrintStopped; } /// @@ -804,20 +815,21 @@ public static async Task Run() // Check for changes of the print status. // The packet providing file info has be sent first because it includes a time_t value that must reside on a 64-bit boundary! - if (_printStarted) + if (_startPrint) { using (await Model.Provider.AccessReadOnlyAsync()) { - _printStarted = !DataTransfer.WritePrintStarted(Model.Provider.Get.Job.File); + _startPrint = !DataTransfer.WritePrintStarted(Model.Provider.Get.Job.File); } } else { - using (await _printStopppedReasonLock.LockAsync(Program.CancellationToken)) + using (await _stopPrintLock.LockAsync(Program.CancellationToken)) { - if (_printStoppedReason != null && DataTransfer.WritePrintStopped(_printStoppedReason.Value)) + if (_stopPrintRequest != null && DataTransfer.WritePrintStopped(_stopPrintReason)) { - _printStoppedReason = null; + _stopPrintRequest.SetResult(); + _stopPrintRequest = null; } } } @@ -1532,18 +1544,27 @@ private static async Task Invalidate(string message) } } - // Clear messages to send to the firmware - lock (_messagesToSend) + // No longer stopping a print + _startPrint = false; + if (_stopPrintRequest != null) { - _messagesToSend.Clear(); + _stopPrintRequest.SetResult(); + _stopPrintRequest = null; } // Clear filament assign requests + _assignFilaments = false; lock (_extruderFilamentMapping) { _extruderFilamentMapping.Clear(); } + // Clear messages to send to the firmware + lock (_messagesToSend) + { + _messagesToSend.Clear(); + } + // Notify the updater task Model.Updater.ConnectionLost();