/// <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>
        /// Handle a G-code reply
        /// </summary>
        /// <param name="flags">Message flags</param>
        /// <param name="reply">Code reply</param>
        /// <returns>Whether the reply could be processed</returns>
        public bool HandleReply(MessageTypeFlags flags, string reply)
        {
            if (flags.HasFlag(MessageTypeFlags.LogMessage))
            {
                _partialLogMessage += reply;
                if (!flags.HasFlag(MessageTypeFlags.PushFlag))
                {
                    if (!string.IsNullOrWhiteSpace(_partialLogMessage))
                    {
                        MessageType type = flags.HasFlag(MessageTypeFlags.ErrorMessageFlag) ? MessageType.Error
                                            : flags.HasFlag(MessageTypeFlags.WarningMessageFlag) ? MessageType.Warning
                                                : MessageType.Success;
                        Utility.Logger.Log(type, _partialLogMessage);
                    }
                    _partialLogMessage = null;
                }
            }

            if (SystemMacroHadError)
            {
                SystemMacroHadError = false;
                return(true);
            }

            if (NestedMacros.TryPeek(out MacroFile macroFile))
            {
                if ((macroFile.StartCode != null && !macroFile.StartCode.DoingNestedMacro) || (macroFile.StartCode == null && SystemMacroHasFinished))
                {
                    if (macroFile.StartCode != null)
                    {
                        macroFile.StartCode.HandleReply(flags, reply);
                        if (macroFile.IsFinished)
                        {
                            NestedMacros.Pop().Dispose();
                            _logger.Info("Finished macro file {0}", Path.GetFileName(macroFile.FileName));
                            if (macroFile.StartCode != null)
                            {
                                _logger.Debug("==> Starting code: {0}", macroFile.StartCode);
                            }
                        }
                    }
                    else if (!flags.HasFlag(MessageTypeFlags.PushFlag))
                    {
                        NestedMacros.Pop().Dispose();
                        SystemMacroHasFinished = false;
                        _logger.Info("Finished system macro file {0}", Path.GetFileName(macroFile.FileName));
                    }
                    return(true);
                }

                if (macroFile.StartCode != null)
                {
                    macroFile.StartCode.HandleReply(flags, reply);
                }
            }

            if (BufferedCodes.Count > 0)
            {
                BufferedCodes[0].HandleReply(flags, reply);
                if (BufferedCodes[0].IsFinished)
                {
                    BytesBuffered -= BufferedCodes[0].BinarySize;
                    BufferedCodes.RemoveAt(0);
                }
                return(true);
            }

            // Replies from the code queue or a final empty response from the file are expected
            if (Channel != CodeChannel.CodeQueue && Channel != CodeChannel.File)
            {
                _logger.Warn("Out-of-order reply: '{0}'", reply);
            }
            return(false);
        }