/// <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); }
/// <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; }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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..]));
/// <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); }