/// <summary> /// Run model updates in a certain interval. /// This function updates host properties like network interfaces and storage devices /// </summary> /// <returns>Asynchronous task</returns> public static async Task UpdatePeriodically() { do { // Run another update cycle using (await Provider.AccessReadWriteAsync()) { UpdateNetwork(); UpdateStorages(); CleanMessages(); } // Wait for next update schedule DateTime lastUpdateTime = DateTime.Now; await Task.Delay(Settings.HostUpdateInterval, Program.CancelSource.Token); if (DateTime.Now - lastUpdateTime > TimeSpan.FromMilliseconds(Settings.HostUpdateInterval + 1000)) { // System time has been changed - adjust date and time on the Duet Console.WriteLine("[info] System time has been changed"); Code code = new Code { InternallyExecuted = true, Channel = DuetAPI.CodeChannel.Daemon, Type = CodeType.MCode, MajorNumber = 905 }; code.Parameters.Add(new CodeParameter('P', DateTime.Now.ToString("yyyy-MM-dd"))); code.Parameters.Add(new CodeParameter('S', DateTime.Now.ToString("HH:mm:ss"))); await code.Execute(); } } while (!Program.CancelSource.IsCancellationRequested); }
public void SimpleCode() { Span <byte> span = new byte[128]; Code code = new Code("G53 G10") { Channel = CodeChannel.HTTP }; int bytesWritten = Writer.WriteCode(span, code); Assert.AreEqual(16, bytesWritten); // Header Assert.AreEqual((byte)CodeChannel.HTTP, span[0]); Assert.AreEqual((byte)(SpiCodeFlags.HasMajorCommandNumber | SpiCodeFlags.EnforceAbsolutePosition), span[1]); Assert.AreEqual(0, span[2]); // Number of parameters byte codeLetter = (byte)'G'; Assert.AreEqual(codeLetter, span[3]); int majorCode = MemoryMarshal.Read <int>(span.Slice(4, 4)); Assert.AreEqual(10, majorCode); int minorCode = MemoryMarshal.Read <int>(span.Slice(8, 4)); Assert.AreEqual(-1, minorCode); uint filePosition = MemoryMarshal.Read <uint>(span.Slice(12, 4)); Assert.AreEqual(0xFFFFFFFF, filePosition); // No padding }
/// <summary> /// Read another code from the file being executed asynchronously /// </summary> /// <returns>Next available code or null if the file has ended</returns> public override Code ReadCode() { // Read the next code from the file Code result = base.ReadCode(); if (result != null) { result.FilePosition = null; result.Flags |= CodeFlags.IsFromMacro; if (IsConfig) { result.Flags |= CodeFlags.IsFromConfig; } if (IsConfigOverride) { result.Flags |= CodeFlags.IsFromConfigOverride; } if (StartCode != null) { result.Flags |= CodeFlags.IsNestedMacro; } result.SourceConnection = (StartCode != null) ? StartCode.Code.SourceConnection : 0; return(result); } // Remove reference to this file again lock (_macroFiles) { _macroFiles.Remove(this); } return(null); }
/// <summary> /// Enqueue a G/M/T-code synchronously and obtain a task that completes when the code has finished /// </summary> /// <param name="code">Code to execute</param> /// <returns>Asynchronous task</returns> public static Task <CodeResult> ProcessCode(Code code) { QueuedCode item = null; using (Channels[code.Channel].Lock()) { if (code.Flags.HasFlag(CodeFlags.IsFromMacro)) { // Macro codes are already enqueued at the time this is called foreach (QueuedCode queuedCode in Channels[code.Channel].NestedMacroCodes) { if (queuedCode.Code == code) { item = queuedCode; break; } } // Users may want to enqueue custom codes as well when dealing with macro files if (item == null) { item = new QueuedCode(code); Channels[code.Channel].NestedMacroCodes.Enqueue(item); } } else { // Enqueue this code for regular execution item = new QueuedCode(code); Channels[code.Channel].PendingCodes.Enqueue(item); } } item.IsReadyToSend = true; return(item.Task); }
public void ParseG53() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("G53"); Assert.AreEqual(CodeType.GCode, code.Type); Assert.AreEqual(53, code.MajorNumber); Assert.IsNull(code.MinorNumber); }
public void ParseG54() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("G54.6"); Assert.AreEqual(CodeType.GCode, code.Type); Assert.AreEqual(54, code.MajorNumber); Assert.AreEqual(6, code.MinorNumber); }
/// <summary> /// Check if the connection may intercept the given code /// </summary> /// <param name="code">Code to check</param> /// <returns>Whether the code may be intercepted</returns> private bool CanIntercept(Code code) { if (!Connection.IsConnected || code.Flags.HasFlag(CodeFlags.IsPrioritized) != _priorityCodes) { return(false); } if (!_channels.Contains(code.Channel)) { return(false); } if (_filters.Count > 0) { string shortCodeString = (code.Type == CodeType.Comment) ? "Q0" : code.ToShortString(); foreach (string filter in _filters) { if (filter.Equals(shortCodeString, StringComparison.InvariantCultureIgnoreCase)) { return(true); } int asteriskIndex = filter.IndexOf('*'); if (asteriskIndex >= 0 && filter.Substring(0, asteriskIndex).Equals(shortCodeString.Substring(0, asteriskIndex), StringComparison.InvariantCultureIgnoreCase)) { return(true); } } return(false); } return(true); }
/// <summary> /// Called by the <see cref="Code"/> implementation to check if the client wants to intercept a G/M/T-code /// </summary> /// <param name="code">Code to intercept</param> /// <returns>True if the code has been resolved</returns> /// <exception cref="OperationCanceledException">Code has been cancelled</exception> private async Task <bool> Intercept(Code code) { using (await _codeMonitor.EnterAsync(Program.CancellationToken)) { // Send it to the IPC client _codeBeingIntercepted = code; _codeMonitor.Pulse(); // Wait for a code result to be set by the interceptor await _codeMonitor.WaitAsync(Program.CancellationToken); try { // Code is cancelled. This invokes an OperationCanceledException on the code's task. if (_interceptionResult is Cancel) { throw new OperationCanceledException(); } // Code is resolved with a given result and the request is acknowledged if (_interceptionResult is Resolve resolveCommand) { code.Result = (resolveCommand.Content == null) ? new CodeResult() : new CodeResult(resolveCommand.Type, resolveCommand.Content); return(true); } // Code is ignored. Don't do anything } catch (Exception e) when(!(e is OperationCanceledException)) { Connection.Logger.Error(e, "Interception processor caught an exception"); } } return(false); }
/// <summary> /// Wait for all pending codes on the same stack level as the given code to finish /// </summary> /// <param name="code">Code waiting for the flush</param> /// <returns>Whether the codes have been flushed successfully</returns> public static Task <bool> Flush(Code code) { using (_channels[code.Channel].Lock()) { return(_channels[code.Channel].Flush(code)); } }
/// <summary> /// Checks if the given code contains any Linux object model fields /// </summary> /// <param name="code">Code to check</param> /// <returns>Whether the code contains any Linux object model fields</returns> /// <exception cref="CodeParserException">Failed to parse expression</exception> public static bool ContainsLinuxFields(Code code) { // echo command if (code.Keyword == KeywordType.Echo) { foreach (string expression in SplitExpression(code.KeywordArgument)) { if (ContainsLinuxFields(expression, code)) { return(true); } } return(false); } // Conditional code if (code.Keyword != KeywordType.None && code.KeywordArgument != null) { return(ContainsLinuxFields(code.KeywordArgument, code)); } // Regular G/M/T-code foreach (CodeParameter parameter in code.Parameters) { if (parameter.IsExpression && ContainsLinuxFields(parameter, code)) { return(true); } } return(false); }
public void ParseKeywords() { DuetAPI.Commands.Code code = new DuetAPI.Commands.Code("if machine.tool.is.great <= {(0.03 - 0.001) + {foo}} (some nice) ; comment"); Assert.AreEqual(0, code.Indent); Assert.AreEqual(KeywordType.If, code.Keyword); Assert.AreEqual("machine.tool.is.great <= {(0.03 - 0.001) + {foo}}", code.KeywordArgument); Assert.AreEqual("some nice comment", code.Comment); code = new DuetAPI.Commands.Code(" elif true"); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.ElseIf, code.Keyword); Assert.AreEqual("true", code.KeywordArgument); code = new DuetAPI.Commands.Code(" else"); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.Else, code.Keyword); Assert.IsNull(code.KeywordArgument); code = new DuetAPI.Commands.Code(" while machine.autocal.stddev > 0.04"); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.While, code.Keyword); Assert.AreEqual("machine.autocal.stddev > 0.04", code.KeywordArgument); code = new DuetAPI.Commands.Code(" break"); Assert.AreEqual(4, code.Indent); Assert.AreEqual(KeywordType.Break, code.Keyword); Assert.IsNull(code.KeywordArgument); code = new DuetAPI.Commands.Code(" continue"); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.Continue, code.Keyword); Assert.IsNull(code.KeywordArgument); code = new DuetAPI.Commands.Code(" return"); Assert.AreEqual(4, code.Indent); Assert.AreEqual(KeywordType.Return, code.Keyword); Assert.IsEmpty(code.KeywordArgument); code = new DuetAPI.Commands.Code(" abort foo bar"); Assert.AreEqual(4, code.Indent); Assert.AreEqual(KeywordType.Abort, code.Keyword); Assert.AreEqual("foo bar", code.KeywordArgument); code = new DuetAPI.Commands.Code(" var asdf=0.34"); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.Var, code.Keyword); Assert.AreEqual("asdf=0.34", code.KeywordArgument); code = new DuetAPI.Commands.Code(" set asdf=\"meh\""); Assert.AreEqual(2, code.Indent); Assert.AreEqual(KeywordType.Set, code.Keyword); Assert.AreEqual("asdf=\"meh\"", code.KeywordArgument); code = new DuetControlServer.Commands.Code("echo {{3 + 3} + (volumes[0].freeSpace - 4)}"); Assert.AreEqual(0, code.Indent); Assert.AreEqual(KeywordType.Echo, code.Keyword); Assert.AreEqual("{{3 + 3} + (volumes[0].freeSpace - 4)}", code.KeywordArgument); }
public void ParseG28() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("G28 X Y"); Assert.AreEqual(CodeType.GCode, code.Type); Assert.AreEqual(28, code.MajorNumber); Assert.AreEqual(null, code.MinorNumber); Assert.AreEqual(2, code.Parameters.Count); Assert.AreEqual('X', code.Parameters[0].Letter); Assert.AreEqual('Y', code.Parameters[1].Letter); }
public void ParseG29() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("G29 S1 ; load heightmap"); Assert.AreEqual(CodeType.GCode, code.Type); Assert.AreEqual(29, code.MajorNumber); Assert.AreEqual(null, code.MinorNumber); Assert.AreEqual(1, code.Parameters.Count); Assert.AreEqual('S', code.Parameters[0].Letter); Assert.IsTrue(code.Parameter('S', 0) == 1); }
/// <summary> /// Send a pending code to the firmware /// </summary> /// <param name="code">Code to send</param> /// <param name="codeLength">Length of the binary code in bytes</param> /// <returns>Whether the code could be sent</returns> internal static bool SendCode(Code code, int codeLength) { if (_bufferSpace > codeLength && DataTransfer.WriteCode(code)) { _bytesReserved += codeLength; _bufferSpace -= codeLength; return(true); } return(false); }
/// <summary> /// Run model updates in a certain interval. /// This function updates host properties like network interfaces and storage devices /// </summary> /// <returns>Asynchronous task</returns> public static async Task Run() { DateTime lastUpdateTime = DateTime.Now; string lastHostname = Environment.MachineName; do { // Run another update cycle using (await Provider.AccessReadWriteAsync()) { UpdateNetwork(); UpdateVolumes(); CleanMessages(); } // Check if the system time has to be updated if (DateTime.Now - lastUpdateTime > TimeSpan.FromMilliseconds(Settings.HostUpdateInterval + 5000) && !System.Diagnostics.Debugger.IsAttached) { _logger.Info("System time has been changed"); Code code = new Code { InternallyProcessed = !Settings.NoSpi, Flags = CodeFlags.Asynchronous, Channel = CodeChannel.Trigger, Type = CodeType.MCode, MajorNumber = 905 }; code.Parameters.Add(new CodeParameter('P', DateTime.Now.ToString("yyyy-MM-dd"))); code.Parameters.Add(new CodeParameter('S', DateTime.Now.ToString("HH:mm:ss"))); await code.Execute(); } // Check if the hostname has to be updated if (lastHostname != Environment.MachineName) { _logger.Info("Hostname has been changed"); lastHostname = Environment.MachineName; Code code = new Code { InternallyProcessed = !Settings.NoSpi, Flags = CodeFlags.Asynchronous, Channel = CodeChannel.Trigger, Type = CodeType.MCode, MajorNumber = 550 }; code.Parameters.Add(new CodeParameter('P', lastHostname)); await code.Execute(); } // Wait for next scheduled update check lastUpdateTime = DateTime.Now; await Task.Delay(Settings.HostUpdateInterval, Program.CancellationToken); }while (!Program.CancelSource.IsCancellationRequested); }
/// <summary> /// Enqueue a G/M/T-code synchronously and obtain a task that completes when the code has finished /// </summary> /// <param name="code">Code to execute</param> /// <returns>Asynchronous task</returns> public static async Task ProcessCode(Code code) { if (code.Type == CodeType.MCode && code.MajorNumber == 703) { // It is safe to assume that the tools and extruders have been configured at this point. // Assign the filaments next so that M703 works as intended _assignFilaments = true; } using (await _channels[code.Channel].LockAsync()) { _channels[code.Channel].ProcessCode(code); } }
public async Task EvaluateLinuxOnly() { DuetControlServer.Model.Provider.Get.Volumes.Clear(); DuetControlServer.Model.Provider.Get.Volumes.Add(new DuetAPI.Machine.Volume { FreeSpace = 12345 }); DuetControlServer.Commands.Code code = new DuetControlServer.Commands.Code("echo volumes[0].freeSpace"); object result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("12345", result); code = new DuetControlServer.Commands.Code("echo move.axes[0].userPosition"); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("move.axes[0].userPosition", result); code = new DuetControlServer.Commands.Code("echo move.axes[{1 + 1}].userPosition"); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("move.axes[{1 + 1}].userPosition", result); code = new DuetControlServer.Commands.Code("echo #volumes"); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("1", result); code = new DuetControlServer.Commands.Code("echo volumes"); Assert.ThrowsAsync <CodeParserException>(async() => await DuetControlServer.Model.Expressions.Evaluate(code, true)); code = new DuetControlServer.Commands.Code("echo scanner"); result = await DuetControlServer.Model.Expressions.Evaluate(code, false); Assert.AreEqual("{object}", result); code = new DuetControlServer.Commands.Code("echo move.axes[0].userPosition + volumes[0].freeSpace"); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("move.axes[0].userPosition + 12345", result); code = new DuetControlServer.Commands.Code("echo \"hello\""); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("\"hello\"", result); code = new DuetControlServer.Commands.Code("echo {\"hello\" ^ (\"there\" + volumes[0].freeSpace)}"); result = await DuetControlServer.Model.Expressions.Evaluate(code, true); Assert.AreEqual("{\"hello\" ^ (\"there\" + 12345)}", result); }
public void ParseT3() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("T3 P4 S\"foo\""); Assert.AreEqual(CodeType.TCode, code.Type); Assert.AreEqual(3, code.MajorNumber); Assert.AreEqual(null, code.MinorNumber); Assert.AreEqual(CodeFlags.None, code.Flags); Assert.AreEqual(2, code.Parameters.Count); Assert.AreEqual('P', code.Parameters[0].Letter); Assert.AreEqual(4, (int)code.Parameters[0]); Assert.AreEqual('S', code.Parameters[1].Letter); Assert.AreEqual("foo", (string)code.Parameters[1]); Assert.AreEqual("T3 P4 S\"foo\"", code.ToString()); }
/// <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(Code code, InterceptionMode type) { List <Interception> processors = new List <Interception>(); lock (_connections[type]) { processors.AddRange(_connections[type].Connections); } foreach (Interception processor in processors) { if (processor.Connection.IsConnected && code.SourceConnection != processor.Connection.Id) { lock (_connections[type]) { _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 { lock (_connections[type]) { _connections[type].InterceptingConnection = -1; _connections[type].CodeBeingIntercepted = null; } } } } return(false); }
public void ParseM569() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("M569 P2 S1 T0.5"); Assert.AreEqual(CodeType.MCode, code.Type); Assert.AreEqual(569, code.MajorNumber); Assert.AreEqual(null, code.MinorNumber); Assert.AreEqual(CodeFlags.None, code.Flags); Assert.AreEqual(3, code.Parameters.Count); Assert.AreEqual('P', code.Parameters[0].Letter); Assert.AreEqual(2, (int)code.Parameters[0]); Assert.AreEqual('S', code.Parameters[1].Letter); Assert.AreEqual(1, (int)code.Parameters[1]); Assert.AreEqual('T', code.Parameters[2].Letter); Assert.AreEqual(0.5, code.Parameters[2], 0.0001); }
public void ParseM106() { DuetAPI.Commands.Code code = new DuetControlServer.Commands.Code("M106 P1 C\"Fancy \"\" Fan\" H-1 S0.5"); Assert.AreEqual(CodeType.MCode, code.Type); Assert.AreEqual(106, code.MajorNumber); Assert.AreEqual(null, code.MinorNumber); Assert.AreEqual(4, code.Parameters.Count); Assert.AreEqual('P', code.Parameters[0].Letter); Assert.AreEqual(1, (int)code.Parameters[0]); Assert.AreEqual('C', code.Parameters[1].Letter); Assert.AreEqual("Fancy \" Fan", (string)code.Parameters[1]); Assert.AreEqual('H', code.Parameters[2].Letter); Assert.AreEqual(-1, (int)code.Parameters[2]); Assert.AreEqual('S', code.Parameters[3].Letter); Assert.AreEqual(0.5, code.Parameters[3], 0.0001); TestContext.Out.Write(JsonConvert.SerializeObject(code, Formatting.Indented)); }
public void Comment() { Span <byte> span = new byte[128]; span.Fill(0xFF); Code code = new Code("; Hello world") { Channel = DuetAPI.CodeChannel.Telnet }; int bytesWritten = Writer.WriteCode(span, code); Assert.AreEqual(36, bytesWritten); // Header Assert.AreEqual((byte)DuetAPI.CodeChannel.Telnet, span[0]); Assert.AreEqual((byte)CodeFlags.HasMajorCommandNumber, span[1]); Assert.AreEqual(1, span[2]); // Number of parameters Assert.AreEqual((byte)'Q', span[3]); // Code letter int majorCode = MemoryMarshal.Read <int>(span.Slice(4, 4)); Assert.AreEqual(0, majorCode); int minorCode = MemoryMarshal.Read <int>(span.Slice(8, 4)); Assert.AreEqual(-1, minorCode); uint filePosition = MemoryMarshal.Read <uint>(span.Slice(12, 4)); Assert.AreEqual(0xFFFFFFFF, filePosition); // Comment parameter Assert.AreEqual((byte)'@', span[16]); Assert.AreEqual((byte)DataType.String, span[17]); int intValue = MemoryMarshal.Read <int>(span.Slice(20, 4)); Assert.AreEqual(11, intValue); // Comment payload ("Hello world") string stringValue = Encoding.UTF8.GetString(span.Slice(24, 11)); Assert.AreEqual("Hello world", stringValue); Assert.AreEqual(0, span[35]); }
public void HasLinuxExpressions() { Assert.Throws <CodeParserException>(() => new DuetControlServer.Commands.Code("G1 Z{move.axes[0].machinePosition -")); Assert.Throws <CodeParserException>(() => new DuetControlServer.Commands.Code("G92 Z{{3 + 3} + (volumes[0].freeSpace - 4}")); Assert.Throws <CodeParserException>(() => new DuetControlServer.Commands.Code("G92 Z{{3 + 3} + (volumes[0].freeSpace - 4)")); Assert.Throws <CodeParserException>(() => new DuetControlServer.Commands.Code("G92 Z{{3 + 3 + (move.axes[0].userPosition - 4)")); DuetControlServer.Commands.Code code = new DuetControlServer.Commands.Code("G1 Z{move.axes[2].userPosition - 3}"); Assert.IsFalse(DuetControlServer.Model.Expressions.ContainsLinuxFields(code)); code = new DuetControlServer.Commands.Code("echo {{3 + 3} + (volumes[0].freeSpace - 4)}"); Assert.IsTrue(DuetControlServer.Model.Expressions.ContainsLinuxFields(code)); code = new DuetControlServer.Commands.Code("G92 Z{{3 + 3} + (volumes[0].freeSpace - 4)}"); Assert.IsTrue(DuetControlServer.Model.Expressions.ContainsLinuxFields(code)); code = new DuetControlServer.Commands.Code("G92 Z{{3 + 3} + (move.axes[0].userPosition - 4)}"); Assert.IsFalse(DuetControlServer.Model.Expressions.ContainsLinuxFields(code)); }
/// <summary> /// Write a parsed G/M/T code in binary format to a memory span /// </summary> /// <param name="to">Destination</param> /// <param name="code">Code to write</param> /// <returns>Number of bytes written</returns> /// <exception cref="ArgumentException">Unsupported data type</exception> public static int WriteCode(Span <byte> to, Code code) { int bytesWritten = 0; // Write code header CodeHeader header = new() { Channel = code.Channel, FilePosition = (uint)(code.FilePosition ?? 0xFFFFFFFF), Letter = (byte)code.Type, MajorCode = (code.Type == CodeType.Comment) ? 0 : (code.MajorNumber ?? -1), MinorCode = code.MinorNumber ?? -1, NumParameters = (byte)((code.Type == CodeType.Comment) ? 1 : code.Parameters.Count) }; if (code.Type == CodeType.Comment || code.MajorNumber != null) { header.Flags |= CodeFlags.HasMajorCommandNumber; } if (code.MinorNumber != null) { header.Flags |= CodeFlags.HasMinorCommandNumber; } if (code.FilePosition != null) { header.Flags |= CodeFlags.HasFilePosition; } if (code.Flags.HasFlag(DuetAPI.Commands.CodeFlags.EnforceAbsolutePosition)) { header.Flags |= CodeFlags.EnforceAbsolutePosition; } MemoryMarshal.Write(to, ref header); bytesWritten += Marshal.SizeOf <CodeHeader>(); // Write line number if (DataTransfer.ProtocolVersion >= 2) { int lineNumber = (int)(code.LineNumber ?? 0); MemoryMarshal.Write(to[bytesWritten..], ref lineNumber); bytesWritten += Marshal.SizeOf <int>(); }
/// <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(Code code, InterceptionMode type) { if (Program.CancellationToken.IsCancellationRequested) { // Don't intercept any more codes if the application is being shut down return(false); } List <CodeInterception> processors = new List <CodeInterception>(); lock (_connections[type]) { processors.AddRange(_connections[type]); } foreach (CodeInterception processor in processors) { if (processor.CanIntercept(code)) { 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; } } } return(false); }
/// <summary> /// Wait for all pending (macro) codes to finish /// </summary> /// <param name="code">Code requesting the flush request</param> /// <returns>Whether the codes have been flushed successfully</returns> public static Task <bool> Flush(Code code) { code.WaitingForFlush = true; TaskCompletionSource <bool> tcs; using (_channels[code.Channel].Lock()) { if (code.Flags.HasFlag(CodeFlags.IsFromMacro)) { if (_channels[code.Channel].NestedMacros.TryPeek(out MacroFile macroFile)) { tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); macroFile.PendingFlushRequests.Enqueue(tcs); return(tcs.Task); } return(Task.FromResult(false)); } tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); _channels[code.Channel].PendingFlushRequests.Enqueue(tcs); } return(tcs.Task); }
/// <summary> /// Run model updates in a certain interval. /// This function updates host properties like network interfaces and storage devices /// </summary> /// <returns>Asynchronous task</returns> public static async Task Run() { DateTime lastUpdateTime = DateTime.Now; do { // Run another update cycle using (await Provider.AccessReadWriteAsync()) { UpdateNetwork(); UpdateStorages(); CleanMessages(); } // Check if the system time has to be updated if (DateTime.Now - lastUpdateTime > TimeSpan.FromMilliseconds(Settings.HostUpdateInterval + 5000) && !System.Diagnostics.Debugger.IsAttached) { _logger.Info("System time has been changed"); Code code = new Code { InternallyProcessed = true, Channel = DuetAPI.CodeChannel.Daemon, Type = CodeType.MCode, MajorNumber = 905 }; code.Parameters.Add(new CodeParameter('P', DateTime.Now.ToString("yyyy-MM-dd"))); code.Parameters.Add(new CodeParameter('S', DateTime.Now.ToString("HH:mm:ss"))); await code.Execute(); } // Wait for next scheduled update check lastUpdateTime = DateTime.Now; await Task.Delay(Settings.HostUpdateInterval, Program.CancelSource.Token); }while (!Program.CancelSource.IsCancellationRequested); }
/// <summary> /// Called by the <see cref="Code"/> implementation to check if the client wants to intercept a G/M/T-code /// </summary> /// <param name="code">Code to intercept</param> /// <returns>True if the code has been resolved</returns> /// <exception cref="OperationCanceledException">Code has been cancelled</exception> private async Task <bool> Intercept(Code code) { // Send it to the IPC client await _codeQueue.AddAsync(code); // Keep on processing commands from the interceptor until a handling result is returned. // This must be either a Cancel, Ignore, or Resolve instruction! try { if (await _commandQueue.OutputAvailableAsync(Program.CancellationToken)) { BaseCommand command = await _commandQueue.TakeAsync(Program.CancellationToken); // Code is cancelled. This invokes an OperationCanceledException on the code's task. if (command is Cancel) { throw new OperationCanceledException(); } // Code is resolved with a given result and the request is acknowledged if (command is Resolve resolveCommand) { code.Result = (resolveCommand.Content == null) ? new CodeResult() : new CodeResult(resolveCommand.Type, resolveCommand.Content); return(true); } // Code is ignored. Don't do anything } } catch (Exception e) when(!(e is OperationCanceledException)) { _codeQueue.CompleteAdding(); Connection.Logger.Error(e, "Interception processor caught an exception"); } return(false); }
public void CodeWithParameters() { Span <byte> span = new byte[128]; span.Fill(0xFF); Code code = new Code("G1 X4 Y23.5 Z12.2 J\"testok\" E12:3.45:5.67") { Channel = CodeChannel.File }; int bytesWritten = Writer.WriteCode(span, code); Assert.AreEqual(76, bytesWritten); // Header Assert.AreEqual((byte)CodeChannel.File, span[0]); Assert.AreEqual((byte)SpiCodeFlags.HasMajorCommandNumber, span[1]); Assert.AreEqual(5, span[2]); // Number of parameters Assert.AreEqual((byte)'G', span[3]); // Code letter int majorCode = MemoryMarshal.Read <int>(span.Slice(4, 4)); Assert.AreEqual(1, majorCode); int minorCode = MemoryMarshal.Read <int>(span.Slice(8, 4)); Assert.AreEqual(-1, minorCode); uint filePosition = MemoryMarshal.Read <uint>(span.Slice(12, 4)); Assert.AreEqual(0xFFFFFFFF, filePosition); // First parameter (X4) Assert.AreEqual((byte)'X', span[16]); Assert.AreEqual((byte)DataType.Int, span[17]); int intValue = MemoryMarshal.Read <int>(span.Slice(20, 4)); Assert.AreEqual(4, intValue); // Second parameter (Y23.5) Assert.AreEqual((byte)'Y', span[24]); Assert.AreEqual((byte)DataType.Float, span[25]); float floatValue = MemoryMarshal.Read <float>(span.Slice(28, 4)); Assert.AreEqual(23.5, floatValue, 0.00001); // Third parameter (Z12.2) Assert.AreEqual((byte)'Z', span[32]); Assert.AreEqual((byte)DataType.Float, span[33]); floatValue = MemoryMarshal.Read <float>(span.Slice(36, 4)); Assert.AreEqual(12.2, floatValue, 0.00001); // Fourth parameter (J"testok") Assert.AreEqual((byte)'J', span[40]); Assert.AreEqual((byte)DataType.String, span[41]); intValue = MemoryMarshal.Read <int>(span.Slice(44, 4)); Assert.AreEqual(6, intValue); // Fifth parameter (E12:3.45:5.67) Assert.AreEqual((byte)'E', span[48]); Assert.AreEqual((byte)DataType.FloatArray, span[49]); intValue = MemoryMarshal.Read <int>(span.Slice(52, 4)); Assert.AreEqual(3, intValue); // Payload of fourth parameter ("test") string stringValue = Encoding.UTF8.GetString(span.Slice(56, 6)); Assert.AreEqual("testok", stringValue); Assert.AreEqual(0, span[62]); Assert.AreEqual(0, span[63]); // Payload of fifth parameter (12:3.45:5.67) floatValue = MemoryMarshal.Read <float>(span.Slice(64, 4)); Assert.AreEqual(12, floatValue, 0.00001); floatValue = MemoryMarshal.Read <float>(span.Slice(68, 4)); Assert.AreEqual(3.45, floatValue, 0.00001); floatValue = MemoryMarshal.Read <float>(span.Slice(72, 4)); Assert.AreEqual(5.67, floatValue, 0.00001); }
/// <summary> /// Parse the header of a G-code file /// </summary> /// <param name="reader">Stream reader</param> /// <param name="partialFileInfo">G-code file information</param> /// <returns>Asynchronous task</returns> private static async Task ParseHeader(StreamReader reader, ParsedFileInfo partialFileInfo) { // Every time CTS.Token is accessed a copy is generated. Hence we cache one until this method completes CancellationToken token = Program.CancelSource.Token; Code code = new Code(); bool inRelativeMode = false, lastLineHadInfo = false, enforcingAbsolutePosition = false; do { token.ThrowIfCancellationRequested(); // Read another line string line = await reader.ReadLineAsync(); if (line == null) { break; } // See what codes to deal with bool gotNewInfo = false; using (StringReader stringReader = new StringReader(line)) { while (Code.Parse(stringReader, code, ref enforcingAbsolutePosition)) { if (code.Type == CodeType.GCode && partialFileInfo.FirstLayerHeight == 0) { if (code.MajorNumber == 91) { // G91 code (relative positioning) inRelativeMode = true; gotNewInfo = true; } else if (inRelativeMode) { // G90 (absolute positioning) inRelativeMode = (code.MajorNumber != 90); gotNewInfo = true; } else if (code.MajorNumber == 0 || code.MajorNumber == 1) { // G0/G1 is a move, see if there is a Z parameter present CodeParameter zParam = code.Parameter('Z'); if (zParam != null) { float z = zParam; if (z <= Settings.MaxLayerHeight) { partialFileInfo.FirstLayerHeight = z; gotNewInfo = true; } } } } else if (!string.IsNullOrWhiteSpace(code.Comment)) { gotNewInfo |= partialFileInfo.LayerHeight == 0 && FindLayerHeight(line, ref partialFileInfo); gotNewInfo |= FindFilamentUsed(line, ref partialFileInfo); gotNewInfo |= string.IsNullOrEmpty(partialFileInfo.GeneratedBy) && FindGeneratedBy(line, ref partialFileInfo); gotNewInfo |= partialFileInfo.PrintTime == 0 && FindPrintTime(line, ref partialFileInfo); gotNewInfo |= partialFileInfo.SimulatedTime == 0 && FindSimulatedTime(line, ref partialFileInfo); } code.Reset(); } } // Is the file info complete? if (!gotNewInfo && !lastLineHadInfo && IsFileInfoComplete(partialFileInfo)) { break; } lastLineHadInfo = gotNewInfo; }while (reader.BaseStream.Position < Settings.FileInfoReadLimitHeader + Settings.FileInfoReadBufferSize); }