/// <summary> /// Attempt to start a file macro /// </summary> /// <param name="filename">Name of the macro file</param> /// <param name="reportMissing">Report an error if the file could not be found</param> /// <param name="fromCode">Request comes from a real G/M/T-code</param> /// <returns>Asynchronous task</returns> public async Task HandleMacroRequest(string filename, bool reportMissing, bool fromCode) { // Get the code starting the macro file QueuedCode startingCode = null; if (fromCode) { if (NestedMacros.TryPeek(out MacroFile macroFile) && macroFile.StartCode != null && !macroFile.StartCode.DoingNestedMacro) { // In case a G/M/T-code invokes more than one macro file... startingCode = macroFile.StartCode; // Check if the other macro file has been finished if (macroFile.IsFinished) { NestedMacros.Pop().Dispose(); Console.WriteLine($"[info] Completed intermediate macro '{macroFile.FileName}'"); } } else if (BufferedCodes.Count > 0) { // The top buffered code is the one that requested the macro file startingCode = BufferedCodes[0]; } if (startingCode != null) { startingCode.DoingNestedMacro = true; } }
/// <summary> /// Enqueue a G/M/T-code synchronously and obtain a task that completes when the code has finished /// </summary> /// <param name="code">Code to execute</param> /// <returns>Asynchronous task</returns> public static Task <CodeResult> ProcessCode(Code code) { QueuedCode item = null; using (Channels[code.Channel].Lock()) { if (code.Flags.HasFlag(CodeFlags.IsFromMacro)) { // Macro codes are already enqueued at the time this is called foreach (QueuedCode queuedCode in Channels[code.Channel].NestedMacroCodes) { if (queuedCode.Code == code) { item = queuedCode; break; } } // Users may want to enqueue custom codes as well when dealing with macro files if (item == null) { item = new QueuedCode(code); Channels[code.Channel].NestedMacroCodes.Enqueue(item); } } else { // Enqueue this code for regular execution item = new QueuedCode(code); Channels[code.Channel].PendingCodes.Enqueue(item); } } item.IsReadyToSend = true; return(item.Task); }
/// <summary> /// Send a queued code to the firmware /// </summary> /// <param name="queuedCode">Code to send</param> /// <param name="codeLength">Length of the binary code in bytes</param> /// <returns>Whether the code could be processed</returns> /// <remarks>The corresponding Channel is locked when this is called</remarks> public static bool BufferCode(QueuedCode queuedCode, out int codeLength) { codeLength = Consts.BufferedCodeHeaderSize + DataTransfer.GetCodeSize(queuedCode.Code); if (_bufferSpace > codeLength && _channels[queuedCode.Code.Channel].BytesBuffered + codeLength <= Settings.MaxBufferSpacePerChannel && DataTransfer.WriteCode(queuedCode.Code)) { _bytesReserved += codeLength; _bufferSpace -= codeLength; queuedCode.BinarySize = codeLength; Console.WriteLine($"[info] Sent {queuedCode.Code}, remaining space {Settings.MaxBufferSpacePerChannel - _channels[queuedCode.Code.Channel].BytesBuffered - codeLength} ({_bufferSpace} total), needed {codeLength}"); return(true); } return(false); }
/// <summary> /// Send a queued code to the firmware /// </summary> /// <param name="queuedCode">Code to send</param> /// <param name="codeLength">Length of the binary code in bytes</param> /// <returns>Whether the code could be processed</returns> /// <remarks>The corresponding Channel is locked when this is called</remarks> public static bool BufferCode(QueuedCode queuedCode, out int codeLength) { codeLength = Consts.BufferedCodeHeaderSize + DataTransfer.GetCodeSize(queuedCode.Code); if (_bufferSpace > codeLength && _channels[queuedCode.Code.Channel].BytesBuffered + codeLength <= Settings.MaxBufferSpacePerChannel && DataTransfer.WriteCode(queuedCode.Code)) { _bytesReserved += codeLength; _bufferSpace -= codeLength; queuedCode.BinarySize = codeLength; return(true); } return(false); }
/// <summary> /// Attempt to start a file macro /// </summary> /// <param name="filename">Name of the macro file</param> /// <param name="reportMissing">Report an error if the file could not be found</param> /// <param name="fromCode">Request comes from a real G/M/T-code</param> /// <returns>Asynchronous task</returns> public async Task HandleMacroRequest(string filename, bool reportMissing, bool fromCode) { // Get the code starting the macro file QueuedCode startingCode = null; if (fromCode) { if (NestedMacros.TryPeek(out MacroFile macroFile) && macroFile.StartCode != null && !macroFile.StartCode.DoingNestedMacro) { // In case a G/M/T-code invokes more than one macro file... startingCode = macroFile.StartCode; // Check if the other macro file has been finished if (macroFile.IsFinished) { NestedMacros.Pop().Dispose(); _logger.Info("Finished intermediate macro file {0}", Path.GetFileName(macroFile.FileName)); } } else if (BufferedCodes.Count > 0) { // The top buffered code is the one that requested the macro file startingCode = BufferedCodes[0]; } if (startingCode != null) { startingCode.DoingNestedMacro = true; // FIXME This work-around will not be needed any more when the SBC interface has got its own task in RRF if ((filename == "stop.g" || filename == "sleep.g") && startingCode.Code.CancellingPrint) { string cancelFile = await FilePath.ToPhysicalAsync("cancel.g", FileDirectory.System); if (File.Exists(cancelFile)) { // Execute cancel.g instead of stop.g if it exists filename = "cancel.g"; } } } }
/// <summary> /// Store an enqueued code for transmission to RepRapFirmware /// </summary> /// <param name="queuedCode">Code to transfer</param> /// <returns>True if the code could be buffered</returns> private bool BufferCode(QueuedCode queuedCode) { try { if (Interface.BufferCode(queuedCode, out int codeLength)) { BytesBuffered += codeLength; BufferedCodes.Add(queuedCode); _logger.Debug("Sent {0}, remaining space {1}, needed {2}", queuedCode.Code, Settings.MaxBufferSpacePerChannel - BytesBuffered, codeLength); return(true); } return(false); } catch (Exception e) { _logger.Debug(e, "Failed to buffer code {0}", queuedCode.Code); queuedCode.SetException(e); return(true); } }
/// <summary> /// Send a queued code to the firmware /// </summary> /// <param name="queuedCode">Code to send</param> /// <returns>Whether the code could be processed</returns> public static bool BufferCode(QueuedCode queuedCode) { try { int codeLength = Communication.Consts.BufferedCodeHeaderSize + DataTransfer.GetCodeSize(queuedCode.Code); if (_bufferSpace > codeLength && DataTransfer.WriteCode(queuedCode.Code)) { Console.WriteLine($"[info] Sent {queuedCode.Code}, remaining space {_bufferSpace}, need {codeLength}"); _bytesReserved += codeLength; _bufferSpace -= codeLength; Channels[queuedCode.Code.Channel].BufferedCodes.Add(queuedCode); return(true); } } catch (Exception e) { queuedCode.SetException(e); return(true); } return(false); }
private bool BufferCode(QueuedCode queuedCode) { if (queuedCode.Code.Type == CodeType.MCode && queuedCode.Code.MajorNumber == 291) { int sParam = queuedCode.Code.Parameter('S', 0); if (sParam == 2 || sParam == 3) { // This M291 call interrupts the G-code flow, wait for M292 next WaitingForMessageAcknowledgement = true; } } else if (queuedCode.Code.Type == CodeType.MCode && queuedCode.Code.MajorNumber == 292) { // The pending message box is about to be closed WaitingForMessageAcknowledgement = false; } else if (WaitingForMessageAcknowledgement) { // Still waiting for M292... return(false); } // Try to send this code to the firmware try { if (Interface.BufferCode(queuedCode, out int codeLength)) { BytesBuffered += codeLength; BufferedCodes.Add(queuedCode); return(true); } return(false); } catch (Exception e) { queuedCode.SetException(e); return(true); } }
/// <summary> /// Process pending requests on this channel /// </summary> /// <returns>If anything more can be done on this channel</returns> public bool ProcessRequests() { // 1. Lock/Unlock requests if (PendingLockRequests.TryPeek(out QueuedLockRequest lockRequest)) { if (lockRequest.IsLockRequest) { if (!lockRequest.IsLockRequested) { lockRequest.IsLockRequested = DataTransfer.WriteLockMovementAndWaitForStandstill(Channel); } } else if (DataTransfer.WriteUnlock(Channel)) { lockRequest.Resolve(true); PendingLockRequests.Dequeue(); } return(false); } // 2. Suspended codes being resumed (may include priority and macro codes) if (_resumingBuffer) { ResumeBuffer(); return(false); } // 3. Priority codes if (PriorityCodes.TryPeek(out QueuedCode queuedCode)) { if (BufferCode(queuedCode)) { PriorityCodes.Dequeue(); return(true); } return(false); } // 4. Macros if (NestedMacros.TryPeek(out MacroFile macroFile)) { // Fill up the macro code buffer Commands.Code code = null; if (macroFile.PendingCodes.Count < Settings.BufferedMacroCodes) { try { code = macroFile.ReadCode(); } catch (Exception e) { _logger.Error(e, "Failed to read code from macro file {0}", Path.GetFileName(macroFile.FileName)); } } if (code != null) { // Start the next code in the background. An interceptor may also generate extra codes queuedCode = new QueuedCode(code); macroFile.PendingCodes.Enqueue(queuedCode); _ = code.Execute().ContinueWith(async task => { try { CodeResult result = await task; if (!queuedCode.IsReadyToSend) { // Macro codes need special treatment because they may complete before they are actually // sent to RepRapFirmware. Remove them from the NestedMacroCodes again in this case queuedCode.HandleReply(result); } if (macroFile.StartCode == null) { await Utility.Logger.LogOutput(result); } } catch (OperationCanceledException) { // Something has gone wrong and the SPI connector has invalidated everything - don't deal with this (yet?) } catch (Exception e) { if (e is AggregateException ae) { e = ae.InnerException; } await Utility.Logger.LogOutput(MessageType.Error, $"Failed to execute {code.ToShortString()}: [{e.GetType().Name}] {e.Message}"); } }); return(true); } if (macroFile.PendingCodes.TryPeek(out queuedCode)) { // Check if another code has finished if (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode))) { macroFile.PendingCodes.Dequeue(); return(true); } // Take care of macro flush requests if (queuedCode.Code.WaitingForFlush && macroFile.PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> macroFlushRequest)) { queuedCode.Code.WaitingForFlush = false; macroFlushRequest.TrySetResult(true); return(false); } } else if (macroFile.IsFinished && BufferedCodes.Count == 0) { // Take care of remaining macro flush requests if (macroFile.PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> macroFlushRequest)) { macroFlushRequest.TrySetResult(true); return(false); } // When the last code from the macro has been processed, notify RRF about the completed file if (((macroFile.StartCode != null && macroFile.StartCode.DoingNestedMacro) || (macroFile.StartCode == null && !SystemMacroHasFinished)) && MacroCompleted(macroFile.StartCode, macroFile.IsAborted)) { if (macroFile.StartCode == null) { SystemMacroHasFinished = true; } if (macroFile.IsAborted) { _logger.Info("Aborted macro file {0}", Path.GetFileName(macroFile.FileName)); } else { _logger.Debug("Finished macro file {0}", Path.GetFileName(macroFile.FileName)); } } } // Don't execute regular requests until the last macro file has finished return(false); } // 5. Regular codes if (PendingCodes.TryPeek(out queuedCode)) { if (BufferCode(queuedCode)) { PendingCodes.Dequeue(); return(true); } return(false); } // 6. Flush requests if (BufferedCodes.Count == 0 && PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> flushRequest)) { flushRequest.SetResult(true); return(false); } // End return(false); }
/// <summary> /// Process pending requests on this channel /// </summary> /// <returns>If anything more can be done on this channel</returns> public bool ProcessRequests() { // 1. Priority codes if (PriorityCodes.TryPeek(out QueuedCode queuedCode)) { if (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode))) { PriorityCodes.Dequeue(); return(true); } // Block this channel until every priority code is gone IsBlocked = true; } // 2. Suspended codes being resumed (may include suspended codes from nested macros) if (_resumingBuffer) { ResumeBuffer(); return(_resumingBuffer); } // FIXME This doesn't work yet for non-M292 codes. Needs more refactoring if (WaitingForMessageAcknowledgement) { // Still waiting for M292... return(false); } // 3. Macro codes if (NestedMacroCodes.TryPeek(out queuedCode) && (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode)))) { NestedMacroCodes.Dequeue(); return(true); } // 4. New codes from macro files if (NestedMacros.TryPeek(out MacroFile macroFile)) { // Try to read the next real code from the system macro being executed Commands.Code code = null; if (!macroFile.IsFinished && NestedMacroCodes.Count < Settings.BufferedMacroCodes) { code = macroFile.ReadCode(); } // If there is any, start executing it in the background. An interceptor may also generate extra codes if (code != null) { // Note that the following code is executed asynchronously to avoid potential // deadlocks which would occur when SPI data is awaited (e.g. heightmap queries) queuedCode = new QueuedCode(code); NestedMacroCodes.Enqueue(queuedCode); _ = Task.Run(async() => { try { CodeResult result = await code.Execute(); if (!queuedCode.IsReadyToSend) { // Macro codes need special treatment because they may complete before they are actually sent to RepRapFirmware queuedCode.HandleReply(result); } if (!macroFile.IsAborted) { await Utility.Logger.LogOutput(result); } } catch (OperationCanceledException) { // Something has gone wrong and the SPI connector has invalidated everything - don't deal with this (yet?) } catch (AggregateException ae) { // FIXME: Should this terminate the macro being executed? Console.WriteLine($"[err] {code} -> {ae.InnerException.Message}"); } }); return(true); } // Macro file is complete if no more codes can be read from the file and the buffered codes are completely gone if (macroFile.IsFinished && !NestedMacroCodes.TryPeek(out _) && BufferedCodes.Count == 0 && ((macroFile.StartCode != null && macroFile.StartCode.DoingNestedMacro) || (macroFile.StartCode == null && !SystemMacroHasFinished)) && MacroCompleted(macroFile.StartCode, macroFile.IsAborted)) { if (macroFile.StartCode == null) { SystemMacroHasFinished = true; } Console.WriteLine($"[info] {(macroFile.IsAborted ? "Aborted" : "Finished")} macro file '{Path.GetFileName(macroFile.FileName)}'"); return(false); } } // 5. Regular codes - only applicable if no macro is being executed else if (PendingCodes.TryPeek(out queuedCode) && BufferCode(queuedCode)) { PendingCodes.Dequeue(); return(true); } // 6. Lock/Unlock requests if (BufferedCodes.Count == 0 && PendingLockRequests.TryPeek(out QueuedLockRequest lockRequest)) { if (lockRequest.IsLockRequest) { if (!lockRequest.IsLockRequested) { lockRequest.IsLockRequested = DataTransfer.WriteLockMovementAndWaitForStandstill(Channel); } } else if (DataTransfer.WriteUnlock(Channel)) { lockRequest.Resolve(true); PendingLockRequests.Dequeue(); } return(false); } // 7. Flush requests if (BufferedCodes.Count == 0 && PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> source)) { source.SetResult(true); return(false); } return(false); }
/// <summary> /// Enqueue a G/M/T-code synchronously and obtain a task that completes when the code has finished /// </summary> /// <param name="code">Code to execute</param> /// <returns>Asynchronous task</returns> public static Task <CodeResult> ProcessCode(Code code) { QueuedCode item = null; using (_channels[code.Channel].Lock()) { if (code.Flags.HasFlag(CodeFlags.IsPrioritized)) { // This code is supposed to override every other queued code item = new QueuedCode(code); _channels[code.Channel].PriorityCodes.Enqueue(item); } else if (code.IsInsertedFromMacro) { // This code is supposed to be executed before the next macro code item = new QueuedCode(code); _channels[code.Channel].InsertMacroCode(item); } else if (code.Flags.HasFlag(CodeFlags.IsFromMacro)) { // Regular macro codes are already enqueued at the time this is called bool firstMacro = true; foreach (MacroFile macroFile in _channels[code.Channel].NestedMacros) { foreach (QueuedCode queuedCode in macroFile.PendingCodes) { if (queuedCode.Code == code) { item = queuedCode; break; } } if (item != null) { if (!firstMacro) { _logger.Warn("Code {0} was internally processed but another macro is still pending", code); } break; } firstMacro = false; } // Users may want to enqueue custom codes as well when dealing with macro files if (item == null) { if (_channels[code.Channel].NestedMacros.TryPeek(out MacroFile macroFile)) { item = new QueuedCode(code); macroFile.PendingCodes.Enqueue(item); } else { throw new ArgumentException("No macro file being executed"); } } } else { // Enqueue this code for regular execution item = new QueuedCode(code); _channels[code.Channel].PendingCodes.Enqueue(item); } } item.IsReadyToSend = true; return(item.Task); }