Пример #1
0
        /// <summary>
        /// Called by the <see cref="Code"/> class to intercept a code.
        /// This method goes through each connected interception channel and notifies the clients.
        /// </summary>
        /// <param name="code">Code to intercept</param>
        /// <param name="type">Type of the interception</param>
        /// <returns>True if the code has been resolved</returns>
        /// <exception cref="OperationCanceledException">Code has been cancelled</exception>
        public static async Task <bool> Intercept(Commands.Code code, InterceptionMode type)
        {
            List <Interception> processors = new List <Interception>();

            using (await _connections[type].LockAsync())
            {
                processors.AddRange(_connections[type].Items);
            }

            foreach (Interception processor in processors)
            {
                if (processor.Connection.IsConnected && code.SourceConnection != processor.Connection.Id)
                {
                    using (await _connections[type].LockAsync())
                    {
                        _connections[type].InterceptingConnection = processor.Connection.Id;
                        _connections[type].CodeBeingIntercepted   = code;
                        try
                        {
                            try
                            {
                                processor.Connection.Logger.Debug("Intercepting code {0} ({1})", code, type);
                                if (await processor.Intercept(code))
                                {
                                    processor.Connection.Logger.Debug("Code has been resolved");
                                    return(true);
                                }
                                processor.Connection.Logger.Debug("Code has been ignored");
                            }
                            catch (OperationCanceledException)
                            {
                                processor.Connection.Logger.Debug("Code has been cancelled");
                                throw;
                            }
                        }
                        finally
                        {
                            _connections[type].InterceptingConnection = -1;
                            _connections[type].CodeBeingIntercepted   = null;
                        }
                    }
                }
            }
            return(false);
        }
Пример #2
0
        /// <summary>
        /// Update the firmware internally
        /// </summary>
        /// <returns>Asynchronous task</returns>
        private static async Task UpdateFirmware()
        {
            Console.Write("Updating firmware... ");
            try
            {
                Commands.Code updateCode = new Commands.Code
                {
                    Type        = CodeType.MCode,
                    MajorNumber = 997
                };
                await updateCode.Execute();

                Console.WriteLine("Done!");
            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
                _logger.Debug(e);
            }
        }
        private bool _lastMessageIncomplete = false;        // true if the last message had the push flag set

        /// <summary>
        /// Constructor for a queued code
        /// </summary>
        /// <param name="code">Code to execute</param>
        public QueuedCode(Commands.Code code)
        {
            Code = code;
        }
Пример #4
0
        /// <summary>
        /// Process a G-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            switch (code.MajorNumber)
            {
            // Save or load heightmap
            case 29:
                CodeParameter cp = code.Parameter('S', 0);
                if (cp == 1 || cp == 3)
                {
                    if (await SPI.Interface.Flush(code))
                    {
                        string file         = code.Parameter('P', FilePath.DefaultHeightmapFile);
                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.System);

                        try
                        {
                            Heightmap map = null;
                            if (cp == 1)
                            {
                                map = new Heightmap();
                                await map.Load(physicalFile);
                            }
                            if (await SPI.Interface.LockMovementAndWaitForStandstill(code.Channel))
                            {
                                if (cp == 1)
                                {
                                    try
                                    {
                                        await SPI.Interface.SetHeightmap(map);
                                    }
                                    finally
                                    {
                                        await SPI.Interface.UnlockAll(code.Channel);
                                    }

                                    string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                                    using (await Model.Provider.AccessReadWriteAsync())
                                    {
                                        Model.Provider.Get.Move.HeightmapFile = virtualFile;
                                    }
                                    return(new CodeResult(DuetAPI.MessageType.Success, $"Height map loaded from file {file}"));
                                }
                                else
                                {
                                    map = await SPI.Interface.GetHeightmap();

                                    await SPI.Interface.UnlockAll(code.Channel);

                                    if (map.NumX * map.NumY > 0)
                                    {
                                        await map.Save(physicalFile);

                                        string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                                        using (await Model.Provider.AccessReadWriteAsync())
                                        {
                                            Model.Provider.Get.Move.HeightmapFile = virtualFile;
                                        }
                                        return(new CodeResult(DuetAPI.MessageType.Success, $"Height map saved to file {file}"));
                                    }
                                    return(new CodeResult());
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            _logger.Debug(e, "Failed to access height map file");
                            if (e is AggregateException ae)
                            {
                                e = ae.InnerException;
                            }
                            return(new CodeResult(DuetAPI.MessageType.Error, $"Failed to {(cp == 1 ? "load" : "save")} height map {(cp == 1 ? "from" : "to")} file {file}: {e.Message}"));
                        }
                    }
                    throw new OperationCanceledException();
                }
                break;
            }
            return(null);
        }
        /// <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);
        }
Пример #6
0
        /// <summary>
        /// Process an M-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            switch (code.MajorNumber)
            {
            // Stop or Unconditional stop
            // Sleep or Conditional stop
            case 0:
            case 1:
                if (await SPI.Interface.Flush(code))
                {
                    using (await Print.LockAsync())
                    {
                        if (Print.IsFileSelected)
                        {
                            // M0/M1 may be used in a print file to terminate it
                            if (code.Channel != CodeChannel.File && !Print.IsPaused)
                            {
                                return(new CodeResult(MessageType.Error, "Pause the print before attempting to cancel it"));
                            }

                            // Invalidate the print file and make sure no more codes are read from it
                            code.CancellingPrint = true;
                            Print.Cancel();
                        }
                    }
                    break;
                }
                throw new OperationCanceledException();

            // List SD card
            case 20:
                if (await SPI.Interface.Flush(code))
                {
                    CodeParameter pParam    = code.Parameter('P');
                    string        directory = await FilePath.ToPhysicalAsync(pParam ?? "", FileDirectory.GCodes);

                    int startAt = Math.Max(code.Parameter('R') ?? 0, 0);

                    // Check if JSON file lists were requested
                    CodeParameter sParam = code.Parameter('S', 0);
                    if (sParam == 2)
                    {
                        string json = FileLists.GetFiles(pParam, directory, startAt);
                        return(new CodeResult(MessageType.Success, json));
                    }
                    if (sParam == 3)
                    {
                        string json = FileLists.GetFileList(pParam, directory, startAt);
                        return(new CodeResult(MessageType.Success, json));
                    }

                    // Print standard G-code response
                    Compatibility compatibility;
                    using (await Model.Provider.AccessReadOnlyAsync())
                    {
                        compatibility = Model.Provider.Get.Channels[code.Channel].Compatibility;
                    }

                    StringBuilder result = new StringBuilder();
                    if (compatibility == Compatibility.Me || compatibility == Compatibility.RepRapFirmware)
                    {
                        result.AppendLine("GCode files:");
                    }
                    else if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                    {
                        result.AppendLine("Begin file list:");
                    }

                    int  numItems  = 0;
                    bool itemFound = false;
                    foreach (string file in Directory.EnumerateFileSystemEntries(directory))
                    {
                        if (numItems++ >= startAt)
                        {
                            string filename = Path.GetFileName(file);
                            if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                            {
                                result.AppendLine(filename);
                            }
                            else
                            {
                                if (itemFound)
                                {
                                    result.Append(',');
                                }
                                result.Append($"\"{filename}\"");
                            }
                            itemFound = true;
                        }
                    }

                    if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                    {
                        if (!itemFound)
                        {
                            result.AppendLine("NONE");
                        }
                        result.Append("End file list");
                    }

                    return(new CodeResult(MessageType.Success, result.ToString()));
                }
                throw new OperationCanceledException();

            // Select a file to print
            case 23:
            case 32:
                if (await SPI.Interface.Flush(code))
                {
                    string file = code.GetUnprecedentedString();
                    if (string.IsNullOrWhiteSpace(file))
                    {
                        return(new CodeResult(MessageType.Error, "Filename expected"));
                    }

                    string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.GCodes);

                    if (!File.Exists(physicalFile))
                    {
                        return(new CodeResult(MessageType.Error, $"Could not find file {file}"));
                    }

                    using (await Print.LockAsync())
                    {
                        if (code.Channel != CodeChannel.File && Print.IsPrinting)
                        {
                            return(new CodeResult(MessageType.Error, "Cannot set file to print, because a file is already being printed"));
                        }
                        await Print.SelectFile(physicalFile);
                    }

                    if (await code.EmulatingMarlin())
                    {
                        return(new CodeResult(MessageType.Success, "File opened\nFile selected"));
                    }
                    return(new CodeResult(MessageType.Success, $"File {file} selected for printing"));
                }
                throw new OperationCanceledException();


            // Resume a file print
            case 24:
                if (await SPI.Interface.Flush(code))
                {
                    using (await Print.LockAsync())
                    {
                        if (!Print.IsFileSelected)
                        {
                            return(new CodeResult(MessageType.Error, "Cannot print, because no file is selected!"));
                        }
                    }

                    // Let RepRapFirmware process this request so it can invoke resume.g. When M24 completes, the file is resumed
                    break;
                }
                throw new OperationCanceledException();

            // Set SD position
            case 26:
                if (await SPI.Interface.Flush(code))
                {
                    using (await Print.LockAsync())
                    {
                        if (!Print.IsFileSelected)
                        {
                            return(new CodeResult(MessageType.Error, "Not printing a file"));
                        }

                        CodeParameter sParam = code.Parameter('S');
                        if (sParam != null)
                        {
                            Print.FilePosition = sParam;
                        }
                    }

                    // P is not supported yet

                    return(new CodeResult());
                }
                throw new OperationCanceledException();

            // Report SD print status
            case 27:
                if (await SPI.Interface.Flush(code))
                {
                    using (await Print.LockAsync())
                    {
                        if (Print.IsFileSelected)
                        {
                            return(new CodeResult(MessageType.Success, $"SD printing byte {Print.FilePosition}/{Print.FileLength}"));
                        }
                        return(new CodeResult(MessageType.Success, "Not SD printing."));
                    }
                }
                throw new OperationCanceledException();

            // Begin write to SD card
            case 28:
                if (await SPI.Interface.Flush(code))
                {
                    int numChannel = (int)code.Channel;
                    using (await Commands.Code.FileLocks[numChannel].LockAsync())
                    {
                        if (Commands.Code.FilesBeingWritten[numChannel] != null)
                        {
                            return(new CodeResult(MessageType.Error, "Another file is already being written to"));
                        }

                        string file = code.GetUnprecedentedString();
                        if (string.IsNullOrWhiteSpace(file))
                        {
                            return(new CodeResult(MessageType.Error, "Filename expected"));
                        }

                        string prefix       = (await code.EmulatingMarlin()) ? "ok\n" : string.Empty;
                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.GCodes);

                        try
                        {
                            FileStream   fileStream = new FileStream(physicalFile, FileMode.Create, FileAccess.Write);
                            StreamWriter writer     = new StreamWriter(fileStream);
                            Commands.Code.FilesBeingWritten[numChannel] = writer;
                            return(new CodeResult(MessageType.Success, prefix + $"Writing to file: {file}"));
                        }
                        catch (Exception e)
                        {
                            _logger.Debug(e, "Failed to open file for writing");
                            return(new CodeResult(MessageType.Error, prefix + $"Can't open file {file} for writing."));
                        }
                    }
                }
                throw new OperationCanceledException();

            // End write to SD card
            case 29:
                if (await SPI.Interface.Flush(code))
                {
                    int numChannel = (int)code.Channel;
                    using (await Commands.Code.FileLocks[numChannel].LockAsync())
                    {
                        if (Commands.Code.FilesBeingWritten[numChannel] != null)
                        {
                            Stream stream = Commands.Code.FilesBeingWritten[numChannel].BaseStream;
                            Commands.Code.FilesBeingWritten[numChannel].Dispose();
                            Commands.Code.FilesBeingWritten[numChannel] = null;
                            stream.Dispose();

                            if (await code.EmulatingMarlin())
                            {
                                return(new CodeResult(MessageType.Success, "Done saving file."));
                            }
                            return(new CodeResult());
                        }
                        break;
                    }
                }
                throw new OperationCanceledException();

            // Delete a file on the SD card
            case 30:
                if (await SPI.Interface.Flush(code))
                {
                    string file         = code.GetUnprecedentedString();
                    string physicalFile = await FilePath.ToPhysicalAsync(file);

                    try
                    {
                        File.Delete(physicalFile);
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to delete file");
                        return(new CodeResult(MessageType.Error, $"Failed to delete file {file}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // For case 32, see case 23

            // Return file information
            case 36:
                if (code.Parameters.Count > 0)
                {
                    if (await SPI.Interface.Flush(code))
                    {
                        string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString());

                        try
                        {
                            ParsedFileInfo info = await FileInfoParser.Parse(file);

                            string json = JsonSerializer.Serialize(info, JsonHelper.DefaultJsonOptions);
                            return(new CodeResult(MessageType.Success, "{\"err\":0," + json.Substring(1)));
                        }
                        catch (Exception e)
                        {
                            _logger.Debug(e, "Failed to return file information");
                            return(new CodeResult(MessageType.Success, "{\"err\":1}"));
                        }
                    }
                    throw new OperationCanceledException();
                }
                break;

            // Simulate file
            case 37:
                if (await SPI.Interface.Flush(code))
                {
                    CodeParameter pParam = code.Parameter('P');
                    if (pParam != null)
                    {
                        string file = pParam;
                        if (string.IsNullOrWhiteSpace(file))
                        {
                            return(new CodeResult(MessageType.Error, "Filename expected"));
                        }

                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.GCodes);

                        if (!File.Exists(physicalFile))
                        {
                            return(new CodeResult(MessageType.Error, $"GCode file \"{file}\" not found\n"));
                        }

                        using (await Print.LockAsync())
                        {
                            if (code.Channel != CodeChannel.File && Print.IsPrinting)
                            {
                                return(new CodeResult(MessageType.Error, "Cannot set file to simulate, because a file is already being printed"));
                            }

                            await Print.SelectFile(physicalFile, true);

                            // Simulation is started when M37 has been processed by the firmware
                        }
                    }
                    break;
                }
                throw new OperationCanceledException();

            // Compute SHA1 hash of target file
            case 38:
                if (await SPI.Interface.Flush(code))
                {
                    string file         = code.GetUnprecedentedString();
                    string physicalFile = await FilePath.ToPhysicalAsync(file);

                    try
                    {
                        using FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read);

                        byte[] hash;
                        using System.Security.Cryptography.SHA1 sha1 = System.Security.Cryptography.SHA1.Create();
                        hash = await Task.Run(() => sha1.ComputeHash(stream), Program.CancelSource.Token);

                        return(new CodeResult(MessageType.Success, BitConverter.ToString(hash).Replace("-", string.Empty)));
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to compute SHA1 checksum");
                        if (e is AggregateException ae)
                        {
                            e = ae.InnerException;
                        }
                        return(new CodeResult(MessageType.Error, $"Could not compute SHA1 checksum for file {file}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // Report SD card information
            case 39:
                if (await SPI.Interface.Flush(code))
                {
                    using (await Model.Provider.AccessReadOnlyAsync())
                    {
                        int index = code.Parameter('P', 0);
                        if (code.Parameter('S', 0) == 2)
                        {
                            if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                            {
                                return(new CodeResult(MessageType.Success, $"{{\"SDinfo\":{{\"slot\":{index},present:0}}}}"));
                            }

                            Storage storage = Model.Provider.Get.Storages[index];
                            var     output  = new
                            {
                                SDinfo = new
                                {
                                    slot     = index,
                                    present  = 1,
                                    capacity = storage.Capacity,
                                    free     = storage.Free,
                                    speed    = storage.Speed
                                }
                            };
                            return(new CodeResult(MessageType.Success, JsonSerializer.Serialize(output, JsonHelper.DefaultJsonOptions)));
                        }
                        else
                        {
                            if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                            {
                                return(new CodeResult(MessageType.Error, $"Bad SD slot number: {index}"));
                            }

                            Storage storage = Model.Provider.Get.Storages[index];
                            return(new CodeResult(MessageType.Success, $"SD card in slot {index}: capacity {storage.Capacity / (1000 * 1000 * 1000):F2}Gb, free space {storage.Free / (1000 * 1000 * 1000):F2}Gb, speed {storage.Speed / (1000 * 1000):F2}MBytes/sec"));
                        }
                    }
                }
                throw new OperationCanceledException();

            // Emergency Stop - unconditional and interpreteted immediately when read
            case 112:
                await SPI.Interface.RequestEmergencyStop();

                using (await Model.Provider.AccessReadWriteAsync())
                {
                    Model.Provider.Get.State.Status = MachineStatus.Halted;
                }
                return(new CodeResult());

            // Immediate DSF diagnostics
            case 122:
                if (code.Parameter('B', 0) == 0 && code.GetUnprecedentedString() == "DSF")
                {
                    CodeResult result = new CodeResult();
                    await Diagnostics(result);

                    return(result);
                }
                break;

            // Display message and optionally wait for response
            case 291:
                if (code.Parameter('S') == 2 || code.Parameter('S') == 3)
                {
                    throw new NotSupportedException();
                }
                break;

            // Save heightmap
            case 374:
                if (await SPI.Interface.Flush(code))
                {
                    string file         = code.Parameter('P', FilePath.DefaultHeightmapFile);
                    string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.System);

                    try
                    {
                        if (await SPI.Interface.LockMovementAndWaitForStandstill(code.Channel))
                        {
                            Heightmap map;
                            try
                            {
                                map = await SPI.Interface.GetHeightmap();
                            }
                            finally
                            {
                                await SPI.Interface.UnlockAll(code.Channel);
                            }

                            if (map.NumX * map.NumY > 0)
                            {
                                await map.Save(physicalFile);

                                string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                                using (await Model.Provider.AccessReadWriteAsync())
                                {
                                    Model.Provider.Get.Move.HeightmapFile = virtualFile;
                                }
                                return(new CodeResult(MessageType.Success, $"Height map saved to file {file}"));
                            }
                            return(new CodeResult());
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to save height map");
                        if (e is AggregateException ae)
                        {
                            e = ae.InnerException;
                        }
                        return(new CodeResult(MessageType.Error, $"Failed to save height map to file {file}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // Load heightmap
            case 375:
                if (await SPI.Interface.Flush(code))
                {
                    string file         = code.Parameter('P', FilePath.DefaultHeightmapFile);
                    string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.System);

                    try
                    {
                        Heightmap map = new Heightmap();
                        await map.Load(physicalFile);

                        if (await SPI.Interface.LockMovementAndWaitForStandstill(code.Channel))
                        {
                            try
                            {
                                await SPI.Interface.SetHeightmap(map);
                            }
                            finally
                            {
                                await SPI.Interface.UnlockAll(code.Channel);
                            }

                            string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                            using (await Model.Provider.AccessReadWriteAsync())
                            {
                                Model.Provider.Get.Move.HeightmapFile = virtualFile;
                            }
                            return(new CodeResult(MessageType.Success, $"Height map loaded from file {file}"));
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to load height map");
                        if (e is AggregateException ae)
                        {
                            e = ae.InnerException;
                        }
                        return(new CodeResult(MessageType.Error, $"Failed to load height map from file {file}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // Create Directory on SD-Card
            case 470:
                if (await SPI.Interface.Flush(code))
                {
                    string path = code.Parameter('P');
                    if (path == null)
                    {
                        return(new CodeResult(MessageType.Error, "Missing directory name"));
                    }
                    string physicalPath = await FilePath.ToPhysicalAsync(path);

                    try
                    {
                        Directory.CreateDirectory(physicalPath);
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to create directory");
                        return(new CodeResult(MessageType.Error, $"Failed to create directory {path}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // Rename File/Directory on SD-Card
            case 471:
                if (await SPI.Interface.Flush(code))
                {
                    string from = code.Parameter('S');
                    string to   = code.Parameter('T');

                    try
                    {
                        string source = await FilePath.ToPhysicalAsync(from);

                        string destination = await FilePath.ToPhysicalAsync(to);

                        if (File.Exists(source))
                        {
                            if (File.Exists(destination) && code.Parameter('D', false))
                            {
                                File.Delete(destination);
                            }
                            File.Move(source, destination);
                        }
                        else if (Directory.Exists(source))
                        {
                            if (Directory.Exists(destination) && code.Parameter('D', false))
                            {
                                // This could be recursive but at the moment we mimic RRF's behaviour
                                Directory.Delete(destination);
                            }
                            Directory.Move(source, destination);
                        }
                        throw new FileNotFoundException();
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to rename file or directory");
                        return(new CodeResult(MessageType.Error, $"Failed to rename file or directory {from} to {to}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // Store parameters
            case 500:
                if (await SPI.Interface.Flush(code))
                {
                    await Utility.ConfigOverride.Save(code);

                    return(new CodeResult());
                }
                throw new OperationCanceledException();

            // Print settings
            case 503:
                if (await SPI.Interface.Flush(code))
                {
                    string configFile = await FilePath.ToPhysicalAsync(FilePath.ConfigFile, FileDirectory.System);

                    if (File.Exists(configFile))
                    {
                        string content = await File.ReadAllTextAsync(configFile);

                        return(new CodeResult(MessageType.Success, content));
                    }

                    string configFileFallback = await FilePath.ToPhysicalAsync(FilePath.ConfigFileFallback, FileDirectory.System);

                    if (File.Exists(configFileFallback))
                    {
                        string content = await File.ReadAllTextAsync(configFileFallback);

                        return(new CodeResult(MessageType.Success, content));
                    }
                    return(new CodeResult(MessageType.Error, "Configuration file not found"));
                }
                throw new OperationCanceledException();

            // Set configuration file folder
            case 505:
                if (await SPI.Interface.Flush(code))
                {
                    string directory = code.Parameter('P'), physicalDirectory = await FilePath.ToPhysicalAsync(directory, "sys");

                    if (Directory.Exists(physicalDirectory))
                    {
                        string virtualDirectory = await FilePath.ToVirtualAsync(physicalDirectory);

                        using (await Model.Provider.AccessReadWriteAsync())
                        {
                            Model.Provider.Get.Directories.System = virtualDirectory;
                        }
                        return(new CodeResult());
                    }
                    return(new CodeResult(MessageType.Error, "Directory not found"));
                }
                throw new OperationCanceledException();

            // Set Name
            case 550:
                if (await SPI.Interface.Flush(code))
                {
                    // Verify the P parameter
                    string pParam = code.Parameter('P');
                    if (pParam.Length > 40)
                    {
                        return(new CodeResult(MessageType.Error, "Machine name is too long"));
                    }

                    // Strip letters and digits from the machine name
                    string machineName = string.Empty;
                    foreach (char c in Environment.MachineName)
                    {
                        if (char.IsLetterOrDigit(c))
                        {
                            machineName += c;
                        }
                    }

                    // Strip letters and digits from the desired name
                    string desiredName = string.Empty;
                    foreach (char c in pParam)
                    {
                        if (char.IsLetterOrDigit(c))
                        {
                            desiredName += c;
                        }
                    }

                    // Make sure the subset of letters and digits is equal
                    if (!machineName.Equals(desiredName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return(new CodeResult(MessageType.Error, "Machine name must consist of the same letters and digits as configured by the Linux hostname"));
                    }

                    // Hostname is legit - pretend we didn't see this code so RRF can interpret it
                    break;
                }
                throw new OperationCanceledException();

            // Configure filament
            case 703:
                if (await SPI.Interface.Flush(code))
                {
                    await Model.Updater.WaitForFullUpdate();

                    break;
                }
                throw new OperationCanceledException();

            // Set current RTC date and time
            case 905:
                if (await SPI.Interface.Flush(code))
                {
                    bool seen = false;

                    CodeParameter pParam = code.Parameter('P');
                    if (pParam != null)
                    {
                        if (DateTime.TryParseExact(pParam, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date))
                        {
                            System.Diagnostics.Process.Start("timedatectl", $"set-time {date:yyyy-MM-dd}").WaitForExit();
                            seen = true;
                        }
                        else
                        {
                            return(new CodeResult(MessageType.Error, "Invalid date format"));
                        }
                    }

                    CodeParameter sParam = code.Parameter('S');
                    if (sParam != null)
                    {
                        if (DateTime.TryParseExact(sParam, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime time))
                        {
                            System.Diagnostics.Process.Start("timedatectl", $"set-time {time:HH:mm:ss}").WaitForExit();
                            seen = true;
                        }
                        else
                        {
                            return(new CodeResult(MessageType.Error, "Invalid time format"));
                        }
                    }

                    if (!seen)
                    {
                        return(new CodeResult(MessageType.Success, $"Current date and time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
                    }
                }
                throw new OperationCanceledException();

            // Start/stop event logging to SD card
            case 929:
                if (await SPI.Interface.Flush(code))
                {
                    CodeParameter sParam = code.Parameter('S');
                    if (sParam == null)
                    {
                        using (await Model.Provider.AccessReadOnlyAsync())
                        {
                            return(new CodeResult(MessageType.Success, $"Event logging is {(Model.Provider.Get.State.LogFile != null ? "enabled" : "disabled")}"));
                        }
                    }

                    if (sParam > 0)
                    {
                        string file = code.Parameter('P', Utility.Logger.DefaultLogFile);
                        if (string.IsNullOrWhiteSpace(file))
                        {
                            return(new CodeResult(MessageType.Error, "Missing filename in M929 command"));
                        }

                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.System);

                        await Utility.Logger.Start(physicalFile);
                    }
                    else
                    {
                        await Utility.Logger.Stop();
                    }

                    return(new CodeResult());
                }
                throw new OperationCanceledException();

            // Update the firmware
            case 997:
                if (((int[])code.Parameter('S', new int[] { 0 })).Contains(0) && code.Parameter('B', 0) == 0)
                {
                    if (await SPI.Interface.Flush(code))
                    {
                        string iapFile, firmwareFile;
                        using (await Model.Provider.AccessReadOnlyAsync())
                        {
                            if (!string.IsNullOrEmpty(Model.Provider.Get.Electronics.ShortName))
                            {
                                // There are now two different IAP binaries, check which one to use
                                iapFile = Model.Provider.Get.Electronics.Firmware.Version.Contains("3.0beta")
                                        ? $"Duet3iap_spi_{Model.Provider.Get.Electronics.ShortName}.bin"
                                        : $"Duet3_SBCiap_{Model.Provider.Get.Electronics.ShortName}.bin";
                                firmwareFile = $"Duet3Firmware_{Model.Provider.Get.Electronics.ShortName}.bin";
                            }
                            else
                            {
                                // ShortName field is not present - this must be a really old firmware version
                                iapFile      = $"Duet3iap_spi.bin";
                                firmwareFile = "Duet3Firmware.bin";
                            }
                        }

                        iapFile = await FilePath.ToPhysicalAsync(iapFile, FileDirectory.System);

                        if (!File.Exists(iapFile))
                        {
                            return(new CodeResult(MessageType.Error, $"Failed to find IAP file {iapFile}"));
                        }

                        firmwareFile = await FilePath.ToPhysicalAsync(firmwareFile, FileDirectory.System);

                        if (!File.Exists(firmwareFile))
                        {
                            return(new CodeResult(MessageType.Error, $"Failed to find firmware file {firmwareFile}"));
                        }

                        using FileStream iapStream      = new FileStream(iapFile, FileMode.Open, FileAccess.Read);
                        using FileStream firmwareStream = new FileStream(firmwareFile, FileMode.Open, FileAccess.Read);
                        await SPI.Interface.UpdateFirmware(iapStream, firmwareStream);

                        return(new CodeResult());
                    }
                    throw new OperationCanceledException();
                }
                break;

            // Request resend of line
            case 998:
                throw new NotSupportedException();

            // Reset controller - unconditional and interpreteted immediately when read
            case 999:
                if (code.Parameters.Count == 0)
                {
                    await SPI.Interface.RequestReset();

                    return(new CodeResult());
                }
                break;
            }
            return(null);
        }
Пример #7
0
        /// <summary>
        /// Process an M-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            switch (code.MajorNumber)
            {
            // Cancel print
            case 0:
            case 1:
                if (Print.IsPrinting)
                {
                    await Print.Cancel();
                }
                break;

            // Select a file to print
            case 23:
            {
                string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString(), "gcodes");

                if (File.Exists(file))
                {
                    using (await _fileToPrintLock.LockAsync())
                    {
                        _fileToPrint = file;
                    }
                    return(new CodeResult(MessageType.Success, $"File {code.GetUnprecedentedString()} selected for printing"));
                }
                return(new CodeResult(MessageType.Error, $"Could not find file {code.GetUnprecedentedString()}"));
            }

            // Resume a file print
            case 24:
                if (!Print.IsPaused)
                {
                    string file;
                    using (await _fileToPrintLock.LockAsync())
                    {
                        file = string.Copy(_fileToPrint);
                    }

                    if (string.IsNullOrEmpty(file))
                    {
                        return(new CodeResult(MessageType.Error, "Cannot print, because no file is selected!"));
                    }

                    // FIXME Emulate Marlin via "File opened\nFile selected". IMHO this should happen via a CodeChannel property
                    return(await Print.Start(file, code.Channel));
                }
                break;

            // Pause print
            case 25:
            case 226:
                if (Print.IsPrinting && !Print.IsPaused)
                {
                    // Stop sending file instructions to the firmware
                    await Print.Pause();
                }
                break;

            // Set SD position
            case 26:
            {
                CodeParameter sParam = code.Parameter('S');
                if (sParam != null)
                {
                    Print.Position = sParam;
                }
                // P is not supported yet
                return(new CodeResult());
            }

            // Report SD print status
            case 27:
                if (Print.IsPrinting)
                {
                    return(new CodeResult(MessageType.Success, $"SD printing byte {Print.Position}/{Print.Length}"));
                }
                return(new CodeResult(MessageType.Success, "Not SD printing."));

            // Delete a file on the SD card
            case 30:
            {
                string file = code.GetUnprecedentedString();
                try
                {
                    File.Delete(await FilePath.ToPhysicalAsync(file));
                    return(new CodeResult());
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to delete file {file}: {e.Message}"));
                }
            }

            // Start a file print
            case 32:
            {
                string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString(), "gcodes");

                if (File.Exists(file))
                {
                    using (await _fileToPrintLock.LockAsync())
                    {
                        _fileToPrint = file;
                    }
                    return(await Print.Start(file, code.Channel));
                }
                return(new CodeResult(MessageType.Error, $"Could not find file {code.GetUnprecedentedString()}"));
            }

            // Return file information
            case 36:
                if (code.Parameters.Count > 0)
                {
                    try
                    {
                        string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString());

                        ParsedFileInfo info = await FileInfoParser.Parse(file);

                        string json = JsonConvert.SerializeObject(info, JsonHelper.DefaultSettings);
                        return(new CodeResult(MessageType.Success, "{\"err\":0," + json.Substring(1)));
                    }
                    catch
                    {
                        return(new CodeResult(MessageType.Success, "{\"err\":1}"));
                    }
                }
                break;

            // Simulate file
            case 37:
                // TODO: Check if file exists
                // TODO: Execute and await pseudo-M37 with IsPreProcessed = true so the firmware enters the right simulation state
                // TODO: Start file print
                return(new CodeResult(MessageType.Warning, "M37 is not supported yet"));

            // Compute SHA1 hash of target file
            case 38:
            {
                string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString());

                try
                {
                    using (FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
                    {
                        byte[] hash;
                        using (var sha1 = System.Security.Cryptography.SHA1.Create())
                        {
                            hash = await Task.Run(() => sha1.ComputeHash(stream), Program.CancelSource.Token);
                        }
                        return(new CodeResult(MessageType.Success, BitConverter.ToString(hash).Replace("-", "")));
                    }
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Could not compute SHA1 checksum for file {file}: {ae.InnerException.Message}"));
                }
            }

            // Report SD card information
            case 39:
                using (await Model.Provider.AccessReadOnlyAsync())
                {
                    int index = code.Parameter('P', 0);
                    if (code.Parameter('S', 0) == 2)
                    {
                        if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                        {
                            return(new CodeResult(MessageType.Success, $"{{\"SDinfo\":{{\"slot\":{index},present:0}}}}"));
                        }

                        Storage storage = Model.Provider.Get.Storages[index];
                        var     output  = new
                        {
                            SDinfo = new
                            {
                                slot     = index,
                                present  = 1,
                                capacity = storage.Capacity,
                                free     = storage.Free,
                                speed    = storage.Speed
                            }
                        };
                        return(new CodeResult(MessageType.Success, JsonConvert.SerializeObject(output)));
                    }
                    else
                    {
                        if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                        {
                            return(new CodeResult(MessageType.Error, $"Bad SD slot number: {index}"));
                        }

                        Storage storage = Model.Provider.Get.Storages[index];
                        return(new CodeResult(MessageType.Success, $"SD card in slot {index}: capacity {storage.Capacity / (1000 * 1000 * 1000):F2}Gb, free space {storage.Free / (1000 * 1000 * 1000):F2}Gb, speed {storage.Speed / (1000 * 1000):F2}MBytes/sec"));
                    }
                }

            // Emergency Stop
            case 112:
                await SPI.Interface.RequestEmergencyStop();

                using (await Model.Provider.AccessReadWriteAsync())
                {
                    Model.Provider.Get.State.Status = MachineStatus.Halted;
                }
                return(new CodeResult());

            // Immediate DSF diagnostics
            case 122:
                if (code.GetUnprecedentedString() == "DSF")
                {
                    CodeResult result = new CodeResult();
                    await Diagnostics(result);

                    return(result);
                }
                break;

            // Message box acknowledgement
            case 292:
                code.Flags |= CodeFlags.IsPrioritized;
                break;

            // Save heightmap
            case 374:
            {
                string file = code.Parameter('P', FilePath.DefaultHeightmapFile);

                try
                {
                    Heightmap map = await SPI.Interface.GetHeightmap();

                    await map.Save(await FilePath.ToPhysicalAsync(file, "sys"));

                    return(new CodeResult(MessageType.Success, $"Height map saved to file {file}"));
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to save height map to file {file}: {ae.InnerException.Message}"));
                }
            }

            // Load heightmap
            case 375:
            {
                string file = await FilePath.ToPhysicalAsync(code.Parameter('P', FilePath.DefaultHeightmapFile), "sys");

                try
                {
                    Heightmap map = new Heightmap();
                    await map.Load(file);

                    await SPI.Interface.SetHeightmap(map);

                    return(new CodeResult());
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to load height map from file {file}: {ae.InnerException.Message}"));
                }
            }

            // Create Directory on SD-Card
            case 470:
            {
                string path = code.Parameter('P', "");
                try
                {
                    Directory.CreateDirectory(await FilePath.ToPhysicalAsync(path));
                    return(new CodeResult());
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to create directory {path}: {e.Message}"));
                }
            }

            // Rename File/Directory on SD-Card
            case 471:
            {
                string from = code.Parameter('S');
                string to   = code.Parameter('T');

                try
                {
                    string source = await FilePath.ToPhysicalAsync(from);

                    string destination = await FilePath.ToPhysicalAsync(to);

                    if (File.Exists(source))
                    {
                        if (File.Exists(destination) && code.Parameter('D', false))
                        {
                            File.Delete(destination);
                        }
                        File.Move(source, destination);
                    }
                    else if (Directory.Exists(source))
                    {
                        if (Directory.Exists(destination) && code.Parameter('D', false))
                        {
                            // This could be recursive but at the moment we mimic RRF's behaviour
                            Directory.Delete(destination);
                        }
                        Directory.Move(source, destination);
                    }
                    throw new FileNotFoundException();
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to rename file or directory {from} to {to}: {e.Message}"));
                }
            }

            // Store parameters
            case 500:
                await Utility.ConfigOverride.Save(code);

                break;

            // Print settings
            case 503:
            {
                string configFile = await FilePath.ToPhysicalAsync(FilePath.ConfigFile, "sys");

                if (File.Exists(configFile))
                {
                    string content = await File.ReadAllTextAsync(configFile);

                    return(new CodeResult(MessageType.Success, content));
                }
                configFile = await FilePath.ToPhysicalAsync(FilePath.ConfigFileFallback, "sys");

                if (File.Exists(configFile))
                {
                    string content = await File.ReadAllTextAsync(configFile);

                    return(new CodeResult(MessageType.Success, content));
                }
                return(new CodeResult(MessageType.Error, "Configuration file not found"));
            }

            // Set Name
            case 550:
            {
                // Verify the P parameter
                string pParam = code.Parameter('P');
                if (pParam.Length > 40)
                {
                    return(new CodeResult(MessageType.Error, "Machine name is too long"));
                }

                // Strip letters and digits from the machine name
                string machineName = "";
                foreach (char c in Environment.MachineName)
                {
                    if (char.IsLetterOrDigit(c))
                    {
                        machineName += c;
                    }
                }

                // Strip letters and digits from the desired name
                string desiredName = "";
                foreach (char c in pParam)
                {
                    if (char.IsLetterOrDigit(c))
                    {
                        desiredName += c;
                    }
                }

                // Make sure the subset of letters and digits is equal
                if (!machineName.Equals(desiredName, StringComparison.CurrentCultureIgnoreCase))
                {
                    return(new CodeResult(MessageType.Error, "Machine name must consist of the same letters and digits as configured by the Linux hostname"));
                }
            }
            break;

            // Set current RTC date and time
            case 905:
            {
                bool seen = false;

                CodeParameter pParam = code.Parameter('P');
                if (pParam != null)
                {
                    if (DateTime.TryParseExact(pParam, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date))
                    {
                        System.Diagnostics.Process.Start("timedatectl", $"set-time {date:yyyy-MM-dd}").WaitForExit();
                        seen = true;
                    }
                    else
                    {
                        return(new CodeResult(MessageType.Error, "Invalid date format"));
                    }
                }

                CodeParameter sParam = code.Parameter('S');
                if (sParam != null)
                {
                    if (DateTime.TryParseExact(sParam, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime time))
                    {
                        System.Diagnostics.Process.Start("timedatectl", $"set-time {time:HH:mm:ss}").WaitForExit();
                        seen = true;
                    }
                    else
                    {
                        return(new CodeResult(MessageType.Error, "Invalid time format"));
                    }
                }

                if (!seen)
                {
                    return(new CodeResult(MessageType.Success, $"Current date and time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
                }
            }
            break;

            // Start/stop event logging to SD card
            case 929:
            {
                CodeParameter sParam = code.Parameter('S');
                if (sParam == null)
                {
                    using (await Model.Provider.AccessReadOnlyAsync())
                    {
                        return(new CodeResult(MessageType.Success, $"Event logging is {(Model.Provider.Get.State.LogFile != null ? "enabled" : "disabled")}"));
                    }
                }

                if (sParam > 0)
                {
                    string filename = code.Parameter('P', Utility.Logger.DefaultLogFile);
                    if (string.IsNullOrWhiteSpace(filename))
                    {
                        return(new CodeResult(MessageType.Error, "Missing filename in M929 command"));
                    }

                    using (await Model.Provider.AccessReadWriteAsync())
                    {
                        string physicalFilename = await FilePath.ToPhysicalAsync(filename, "sys");

                        await Utility.Logger.Start(physicalFilename);

                        Model.Provider.Get.State.LogFile = filename;
                    }

                    return(new CodeResult());
                }

                using (await Model.Provider.AccessReadWriteAsync())
                {
                    await Utility.Logger.Stop();

                    Model.Provider.Get.State.LogFile = null;
                }
                return(new CodeResult());
            }

            // Update the firmware
            case 997:
                if (((int[])code.Parameter('S', new int[] { 0 })).Contains(0))
                {
                    string iapFile = await FilePath.ToPhysicalAsync(Settings.IapFile, "sys");

                    if (!File.Exists(iapFile))
                    {
                        return(new CodeResult(MessageType.Error, $"Failed to find IAP file {iapFile}"));
                    }

                    string firmwareFile = await FilePath.ToPhysicalAsync(code.Parameter('P') ?? Settings.FirmwareFile, "sys");

                    if (!File.Exists(firmwareFile))
                    {
                        return(new CodeResult(MessageType.Error, $"Failed to find firmware file {code.Parameter('P') ?? Settings.FirmwareFile}"));
                    }

                    FileStream iapStream      = new FileStream(iapFile, FileMode.Open, FileAccess.Read);
                    FileStream firmwareStream = new FileStream(firmwareFile, FileMode.Open, FileAccess.Read);
                    SPI.Interface.UpdateFirmware(iapStream, firmwareStream);
                }
                // TODO: Implement mechanism for expansion boards
                return(new CodeResult());

            // Reset controller
            case 999:
                if (code.Parameters.Count == 0)
                {
                    await SPI.Interface.RequestReset();

                    return(new CodeResult());
                }
                break;
            }
            return(null);
        }
Пример #8
0
        /// <summary>
        /// Process a G-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            if (code.Channel == CodeChannel.File && FileExecution.Job.IsSimulating)
            {
                // Ignore M-codes from files in simulation mode...
                return(null);
            }

            switch (code.MajorNumber)
            {
            // Save or load heightmap
            case 29:
                CodeParameter cp = code.Parameter('S', 0);
                if (cp == 1 || cp == 3)
                {
                    if (await SPI.Interface.Flush(code))
                    {
                        string file         = code.Parameter('P', FilePath.DefaultHeightmapFile);
                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.System);

                        try
                        {
                            Heightmap map = null;
                            if (cp == 1)
                            {
                                map = new Heightmap();
                                await map.Load(physicalFile);
                            }
                            if (await SPI.Interface.LockMovementAndWaitForStandstill(code.Channel))
                            {
                                if (cp == 1)
                                {
                                    try
                                    {
                                        await SPI.Interface.SetHeightmap(map);
                                    }
                                    finally
                                    {
                                        await SPI.Interface.UnlockAll(code.Channel);
                                    }

                                    string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                                    using (await Model.Provider.AccessReadWriteAsync())
                                    {
                                        Model.Provider.Get.Move.Compensation.File = virtualFile;
                                    }

                                    CodeResult result = new CodeResult();
                                    using (await Model.Provider.AccessReadOnlyAsync())
                                    {
                                        if (Model.Provider.Get.Move.Axes.Any(axis => axis.Letter == 'Z' && !axis.Homed))
                                        {
                                            result.Add(MessageType.Warning, "The height map was loaded when the current Z=0 datum was not determined. This may result in a height offset.");
                                        }
                                    }
                                    result.Add(MessageType.Success, $"Height map loaded from file {file}");
                                    return(result);
                                }
                                else
                                {
                                    try
                                    {
                                        map = await SPI.Interface.GetHeightmap();
                                    }
                                    finally
                                    {
                                        await SPI.Interface.UnlockAll(code.Channel);
                                    }

                                    if (map.NumX * map.NumY > 0)
                                    {
                                        await map.Save(physicalFile);

                                        string virtualFile = await FilePath.ToVirtualAsync(physicalFile);

                                        using (await Model.Provider.AccessReadWriteAsync())
                                        {
                                            Model.Provider.Get.Move.Compensation.File = virtualFile;
                                        }
                                        return(new CodeResult(MessageType.Success, $"Height map saved to file {file}"));
                                    }
                                    return(new CodeResult());
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            _logger.Debug(e, "Failed to access height map file");
                            if (e is AggregateException ae)
                            {
                                e = ae.InnerException;
                            }
                            return(new CodeResult(MessageType.Error, $"Failed to {(cp == 1 ? "load" : "save")} height map {(cp == 1 ? "from" : "to")} file {file}: {e.Message}"));
                        }
                    }
                    throw new OperationCanceledException();
                }
                break;
            }
            return(null);
        }
Пример #9
0
        /// <summary>
        /// Process an M-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            switch (code.MajorNumber)
            {
            // Cancel print
            case 0:
            case 1:
                if (Print.IsPrinting)
                {
                    await Print.Cancel();
                }
                break;

            // Select a file to print
            case 23:
            {
                string file = await FilePath.ToPhysical(code.GetUnprecedentedString(), "gcodes");

                if (File.Exists(file))
                {
                    using (await _fileToPrintLock.LockAsync())
                    {
                        _fileToPrint = file;
                    }
                    return(new CodeResult(MessageType.Success, $"File {file} selected for printing"));
                }
                return(new CodeResult(MessageType.Error, $"Could not find file {file}"));
            }

            // Resume a file print
            case 24:
                if (!Print.IsPaused)
                {
                    string file;
                    using (await _fileToPrintLock.LockAsync())
                    {
                        file = string.Copy(_fileToPrint);
                    }

                    if (string.IsNullOrEmpty(file))
                    {
                        return(new CodeResult(MessageType.Error, "Cannot print, because no file is selected!"));
                    }

                    // FIXME Emulate Marlin via "File opened\nFile selected". IMHO this should happen via a CodeChannel property
                    return(await Print.Start(file, code.Channel));
                }
                break;

            // Pause print
            case 25:
            case 226:
                if (Print.IsPrinting && !Print.IsPaused)
                {
                    // Stop sending file instructions to the firmware
                    await Print.Pause();
                }
                break;

            // Set SD position
            case 26:
            {
                CodeParameter sParam = code.Parameter('S');
                if (sParam != null)
                {
                    Print.Position = sParam;
                }
                // P is not supported yet
                return(new CodeResult());
            }

            // Report SD print status
            case 27:
                if (Print.IsPrinting)
                {
                    return(new CodeResult(MessageType.Success, $"SD printing byte {Print.Position}/{Print.Length}"));
                }
                return(new CodeResult(MessageType.Success, "Not SD printing."));

            // Delete a file on the SD card
            case 30:
            {
                string file = code.GetUnprecedentedString();
                try
                {
                    File.Delete(await FilePath.ToPhysical(file));
                    return(new CodeResult());
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to delete file {file}: {e.Message}"));
                }
            }

            // Start a file print
            case 32:
            {
                string file = await FilePath.ToPhysical(code.GetUnprecedentedString(), "gcodes");

                using (await _fileToPrintLock.LockAsync())
                {
                    _fileToPrint = file;
                }
                return(await Print.Start(file, code.Channel));
            }

            // Return file information
            case 36:
                if (code.Parameters.Count > 0)
                {
                    try
                    {
                        string file = await FilePath.ToPhysical(code.GetUnprecedentedString());

                        ParsedFileInfo info = await FileInfoParser.Parse(file);

                        string json = JsonConvert.SerializeObject(info, JsonHelper.DefaultSettings);
                        return(new CodeResult(MessageType.Success, "{\"err\":0," + json.Substring(1)));
                    }
                    catch
                    {
                        return(new CodeResult(MessageType.Success, "{\"err\":1}"));
                    }
                }
                break;

            // Simulate file
            case 37:
                // TODO: Check if file exists
                // TODO: Execute and await pseudo-M37 with IsPreProcessed = true so the firmware enters the right simulation state
                // TODO: Start file print
                return(new CodeResult(MessageType.Warning, "M37 is not supported yet"));

            // Compute SHA1 hash of target file
            case 38:
            {
                string file = await FilePath.ToPhysical(code.GetUnprecedentedString());

                try
                {
                    using (FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
                    {
                        var    sha1 = System.Security.Cryptography.SHA1.Create();
                        byte[] hash = await Task.Run(() => sha1.ComputeHash(stream), Program.CancelSource.Token);

                        return(new CodeResult(MessageType.Success, BitConverter.ToString(hash).Replace("-", "")));
                    }
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Could not compute SHA1 checksum for file {file}: {ae.InnerException.Message}"));
                }
            }

            // Report SD card information
            case 39:
                using (await Model.Provider.AccessReadOnlyAsync())
                {
                    int index = code.Parameter('P', 0);
                    if (code.Parameter('S', 0) == 2)
                    {
                        if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                        {
                            return(new CodeResult(MessageType.Success, $"{{\"SDinfo\":{{\"slot\":{index},present:0}}}}"));
                        }

                        Storage storage = Model.Provider.Get.Storages[index];
                        var     output  = new
                        {
                            SDinfo = new
                            {
                                slot     = index,
                                present  = 1,
                                capacity = storage.Capacity,
                                free     = storage.Free,
                                speed    = storage.Speed
                            }
                        };
                        return(new CodeResult(MessageType.Success, JsonConvert.SerializeObject(output)));
                    }
                    else
                    {
                        if (index < 0 || index >= Model.Provider.Get.Storages.Count)
                        {
                            return(new CodeResult(MessageType.Error, $"Bad SD slot number: {index}"));
                        }

                        Storage storage = Model.Provider.Get.Storages[index];
                        return(new CodeResult(MessageType.Success, $"SD card in slot {index}: capacity {storage.Capacity / (1000 * 1000 * 1000):F2}Gb, free space {storage.Free / (1000 * 1000 * 1000):F2}Gb, speed {storage.Speed / (1000*1000):F2}MBytes/sec"));
                    }
                }

            // Return from macro
            case 99:
                if (!MacroFile.AbortLastFile(code.Channel))
                {
                    return(new CodeResult(MessageType.Error, "Not executing a macro file"));
                }
                return(new CodeResult());

            // Emergency Stop
            case 112:
                await SPI.Interface.RequestEmergencyStop();

                using (await Model.Provider.AccessReadWriteAsync())
                {
                    Model.Provider.Get.State.Status = MachineStatus.Halted;
                }
                return(new CodeResult());

            // Immediate DSF diagnostics
            case 122:
                if (code.GetUnprecedentedString() == "DSF")
                {
                    CodeResult result = new CodeResult();
                    await Diagnostics(result);

                    return(result);
                }
                break;

            // Save heightmap
            case 374:
            {
                string file = code.Parameter('P', "heightmap.csv");

                try
                {
                    Heightmap map = await SPI.Interface.GetHeightmap();

                    await map.Save(await FilePath.ToPhysical(file, "sys"));

                    return(new CodeResult(MessageType.Success, $"Height map saved to file {file}"));
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to save height map to file {file}: {ae.InnerException.Message}"));
                }
            }

            // Load heightmap
            case 375:
            {
                string file = await FilePath.ToPhysical(code.Parameter('P', "heightmap.csv"), "sys");

                try
                {
                    Heightmap map = new Heightmap();
                    await map.Load(file);

                    await SPI.Interface.SetHeightmap(map);

                    return(new CodeResult());
                }
                catch (AggregateException ae)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to load height map from file {file}: {ae.InnerException.Message}"));
                }
            }

            // Create Directory on SD-Card
            case 470:
            {
                string path = code.Parameter('P', "");
                try
                {
                    Directory.CreateDirectory(await FilePath.ToPhysical(path));
                    return(new CodeResult());
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to create directory {path}: {e.Message}"));
                }
            }

            // Rename File/Directory on SD-Card
            case 471:
            {
                string from = code.Parameter('S');
                string to   = code.Parameter('T');

                try
                {
                    string source = await FilePath.ToPhysical(from);

                    string destination = await FilePath.ToPhysical(to);

                    if (File.Exists(source))
                    {
                        if (File.Exists(destination) && code.Parameter('D', false))
                        {
                            File.Delete(destination);
                        }
                        File.Move(source, destination);
                    }
                    else if (Directory.Exists(source))
                    {
                        if (Directory.Exists(destination) && code.Parameter('D', false))
                        {
                            // This could be recursive but at the moment we mimic RRF's behaviour
                            Directory.Delete(destination);
                        }
                        Directory.Move(source, destination);
                    }
                    throw new FileNotFoundException();
                }
                catch (Exception e)
                {
                    return(new CodeResult(MessageType.Error, $"Failed to rename file or directory {from} to {to}: {e.Message}"));
                }
            }

            // Store parameters
            case 500:
                await Utility.ConfigOverride.Save(code);

                break;

            // Print settings
            case 503:
            {
                string configFile = await FilePath.ToPhysical(MacroFile.ConfigFile, "sys");

                if (File.Exists(configFile))
                {
                    string content = await File.ReadAllTextAsync(configFile);

                    return(new CodeResult(MessageType.Success, content));
                }
                return(new CodeResult(MessageType.Error, "Configuration file not found"));
            }

            // Reset controller
            case 999:
                if (code.Parameters.Count == 0)
                {
                    await SPI.Interface.RequestReset();

                    return(new CodeResult());
                }
                break;
            }
            return(null);
        }
Пример #10
0
        /// <summary>
        /// Process an M-code that should be interpreted by the control server
        /// </summary>
        /// <param name="code">Code to process</param>
        /// <returns>Result of the code if the code completed, else null</returns>
        public static async Task <CodeResult> Process(Commands.Code code)
        {
            if (code.Channel == CodeChannel.File && FileExecution.Job.IsSimulating)
            {
                // Ignore M-codes from files in simulation mode...
                return(null);
            }

            switch (code.MajorNumber)
            {
            // Stop or Unconditional stop
            // Sleep or Conditional stop
            case 0:
            case 1:
                if (await SPI.Interface.Flush(code))
                {
                    using (await FileExecution.Job.LockAsync())
                    {
                        if (FileExecution.Job.IsFileSelected)
                        {
                            // M0/M1 may be used in a print file to terminate it
                            if (code.Channel != CodeChannel.File && !FileExecution.Job.IsPaused)
                            {
                                return(new CodeResult(MessageType.Error, "Pause the print before attempting to cancel it"));
                            }
                        }
                    }
                    break;
                }
                throw new OperationCanceledException();

            // List SD card
            case 20:
                if (await SPI.Interface.Flush(code))
                {
                    // Resolve the directory
                    string virtualDirectory = code.Parameter('P');
                    if (virtualDirectory == null)
                    {
                        using (await Model.Provider.AccessReadOnlyAsync())
                        {
                            virtualDirectory = Model.Provider.Get.Directories.GCodes;
                        }
                    }
                    string physicalDirectory = await FilePath.ToPhysicalAsync(virtualDirectory);

                    // Make sure to stay within limits if it is a request from the firmware
                    int maxSize = -1;
                    if (code.Flags.HasFlag(CodeFlags.IsFromFirmware))
                    {
                        maxSize = Settings.MaxMessageLength;
                    }

                    // Check if JSON file lists were requested
                    int           startAt = Math.Max(code.Parameter('R') ?? 0, 0);
                    CodeParameter sParam  = code.Parameter('S', 0);
                    if (sParam == 2)
                    {
                        string json = FileLists.GetFiles(virtualDirectory, physicalDirectory, startAt, true, maxSize);
                        return(new CodeResult(MessageType.Success, json));
                    }
                    if (sParam == 3)
                    {
                        string json = FileLists.GetFileList(virtualDirectory, physicalDirectory, startAt, maxSize);
                        return(new CodeResult(MessageType.Success, json));
                    }

                    // Print standard G-code response
                    Compatibility compatibility;
                    using (await Model.Provider.AccessReadOnlyAsync())
                    {
                        compatibility = Model.Provider.Get.Inputs[code.Channel].Compatibility;
                    }

                    StringBuilder result = new StringBuilder();
                    if (compatibility == Compatibility.Default || compatibility == Compatibility.RepRapFirmware)
                    {
                        result.AppendLine("GCode files:");
                    }
                    else if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                    {
                        result.AppendLine("Begin file list:");
                    }

                    int  numItems  = 0;
                    bool itemFound = false;
                    foreach (string file in Directory.EnumerateFileSystemEntries(physicalDirectory))
                    {
                        if (numItems++ >= startAt)
                        {
                            string filename = Path.GetFileName(file);
                            if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                            {
                                result.AppendLine(filename);
                            }
                            else
                            {
                                if (itemFound)
                                {
                                    result.Append(',');
                                }
                                result.Append($"\"{filename}\"");
                            }
                            itemFound = true;
                        }
                    }

                    if (compatibility == Compatibility.Marlin || compatibility == Compatibility.NanoDLP)
                    {
                        if (!itemFound)
                        {
                            result.AppendLine("NONE");
                        }
                        result.Append("End file list");
                    }

                    return(new CodeResult(MessageType.Success, result.ToString()));
                }
                throw new OperationCanceledException();

            // Initialize SD card
            case 21:
                if (await SPI.Interface.Flush(code))
                {
                    if (code.Parameter('P', 0) == 0)
                    {
                        // M21 (P0) will always work because it's always mounted
                        return(new CodeResult());
                    }
                    throw new NotSupportedException();
                }
                throw new OperationCanceledException();

            // Release SD card
            case 22:
                throw new NotSupportedException();

            // Select a file to print
            case 23:
            case 32:
                if (await SPI.Interface.Flush(code))
                {
                    string file = code.GetUnprecedentedString();
                    if (string.IsNullOrWhiteSpace(file))
                    {
                        return(new CodeResult(MessageType.Error, "Filename expected"));
                    }

                    string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.GCodes);

                    if (!File.Exists(physicalFile))
                    {
                        return(new CodeResult(MessageType.Error, $"Could not find file {file}"));
                    }

                    using (await FileExecution.Job.LockAsync())
                    {
                        if (code.Channel != CodeChannel.File && FileExecution.Job.IsProcessing)
                        {
                            return(new CodeResult(MessageType.Error, "Cannot set file to print, because a file is already being printed"));
                        }
                        await FileExecution.Job.SelectFile(physicalFile);
                    }

                    if (await code.EmulatingMarlin())
                    {
                        return(new CodeResult(MessageType.Success, "File opened\nFile selected"));
                    }
                    return(new CodeResult(MessageType.Success, $"File {file} selected for printing"));
                }
                throw new OperationCanceledException();


            // Resume a file print
            case 24:
                if (await SPI.Interface.Flush(code))
                {
                    using (await FileExecution.Job.LockAsync())
                    {
                        if (!FileExecution.Job.IsFileSelected)
                        {
                            return(new CodeResult(MessageType.Error, "Cannot print, because no file is selected!"));
                        }
                    }

                    // Let RepRapFirmware process this request so it can invoke resume.g. When M24 completes, the file is resumed
                    break;
                }
                throw new OperationCanceledException();

            // Set SD position
            case 26:
                if (await SPI.Interface.Flush(code))
                {
                    using (await FileExecution.Job.LockAsync())
                    {
                        if (!FileExecution.Job.IsFileSelected)
                        {
                            return(new CodeResult(MessageType.Error, "Not printing a file"));
                        }

                        CodeParameter sParam = code.Parameter('S');
                        if (sParam != null)
                        {
                            if (sParam < 0L || sParam > FileExecution.Job.FileLength)
                            {
                                return(new CodeResult(MessageType.Error, "Position is out of range"));
                            }
                            await FileExecution.Job.SetFilePosition(sParam);
                        }
                    }

                    // P is not supported yet

                    return(new CodeResult());
                }
                throw new OperationCanceledException();

            // Report SD print status
            case 27:
                if (await SPI.Interface.Flush(code))
                {
                    using (await FileExecution.Job.LockAsync())
                    {
                        if (FileExecution.Job.IsFileSelected)
                        {
                            long filePosition = await FileExecution.Job.GetFilePosition();

                            return(new CodeResult(MessageType.Success, $"SD printing byte {filePosition}/{FileExecution.Job.FileLength}"));
                        }
                        return(new CodeResult(MessageType.Success, "Not SD printing."));
                    }
                }
                throw new OperationCanceledException();

            // Begin write to SD card
            case 28:
                if (await SPI.Interface.Flush(code))
                {
                    int numChannel = (int)code.Channel;
                    using (await Commands.Code.FileLocks[numChannel].LockAsync(Program.CancellationToken))
                    {
                        if (Commands.Code.FilesBeingWritten[numChannel] != null)
                        {
                            return(new CodeResult(MessageType.Error, "Another file is already being written to"));
                        }

                        string file = code.GetUnprecedentedString();
                        if (string.IsNullOrWhiteSpace(file))
                        {
                            return(new CodeResult(MessageType.Error, "Filename expected"));
                        }

                        string prefix       = (await code.EmulatingMarlin()) ? "ok\n" : string.Empty;
                        string physicalFile = await FilePath.ToPhysicalAsync(file, FileDirectory.GCodes);

                        try
                        {
                            FileStream   fileStream = new FileStream(physicalFile, FileMode.Create, FileAccess.Write);
                            StreamWriter writer     = new StreamWriter(fileStream);
                            Commands.Code.FilesBeingWritten[numChannel] = writer;
                            return(new CodeResult(MessageType.Success, prefix + $"Writing to file: {file}"));
                        }
                        catch (Exception e)
                        {
                            _logger.Debug(e, "Failed to open file for writing");
                            return(new CodeResult(MessageType.Error, prefix + $"Can't open file {file} for writing."));
                        }
                    }
                }
                throw new OperationCanceledException();

            // End write to SD card
            case 29:
                if (await SPI.Interface.Flush(code))
                {
                    int numChannel = (int)code.Channel;
                    using (await Commands.Code.FileLocks[numChannel].LockAsync(Program.CancellationToken))
                    {
                        if (Commands.Code.FilesBeingWritten[numChannel] != null)
                        {
                            Stream stream = Commands.Code.FilesBeingWritten[numChannel].BaseStream;
                            Commands.Code.FilesBeingWritten[numChannel].Dispose();
                            Commands.Code.FilesBeingWritten[numChannel] = null;
                            stream.Dispose();

                            if (await code.EmulatingMarlin())
                            {
                                return(new CodeResult(MessageType.Success, "Done saving file."));
                            }
                            return(new CodeResult());
                        }
                        break;
                    }
                }
                throw new OperationCanceledException();

            // Delete a file on the SD card
            case 30:
                if (await SPI.Interface.Flush(code))
                {
                    string file         = code.GetUnprecedentedString();
                    string physicalFile = await FilePath.ToPhysicalAsync(file);

                    try
                    {
                        File.Delete(physicalFile);
                    }
                    catch (Exception e)
                    {
                        _logger.Debug(e, "Failed to delete file");
                        return(new CodeResult(MessageType.Error, $"Failed to delete file {file}: {e.Message}"));
                    }
                }
                throw new OperationCanceledException();

            // For case 32, see case 23

            // Return file information
            case 36:
                if (code.Parameters.Count > 0)
                {
                    if (await SPI.Interface.Flush(code))
                    {
                        string file = await FilePath.ToPhysicalAsync(code.GetUnprecedentedString(), FileDirectory.GCodes);

                        try
                        {
                            ParsedFileInfo info = await InfoParser.Parse(file);

                            string json = JsonSerializer.Serialize(info, JsonHelper.DefaultJsonOptions);
                            return(new CodeResult(MessageType.Success, "{\"err\":0," + json[1..]));
Пример #11
0
        /// <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);
        }