/// <summary> /// Process a request for a chunk of a given file /// </summary> /// <returns>Asynchronous task</returns> private static async Task HandleFileChunkRequest() { DataTransfer.ReadFileChunkRequest(out string filename, out uint offset, out uint maxLength); _logger.Trace("Received file chunk request for {0}, offset {1}, maxLength {2}", filename, offset, maxLength); try { string filePath = await FilePath.ToPhysicalAsync(filename, filename.EndsWith(".bin")?FileDirectory.Firmware : FileDirectory.System); using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read) { Position = offset }; byte[] buffer = new byte[maxLength]; int bytesRead = await fs.ReadAsync(buffer, 0, (int)maxLength); DataTransfer.WriteFileChunk(buffer.AsSpan(0, bytesRead), fs.Length); } catch (Exception e) { _logger.Error(e, "Failed to send requested file chunk of {0}", filename); DataTransfer.WriteFileChunk(null, 0); } }
/// <summary> /// Send a queued code to the firmware /// </summary> /// <param name="queuedCode">Code to send</param> /// <returns>Whether the code could be processed</returns> public static bool BufferCode(QueuedCode queuedCode) { try { int codeLength = Communication.Consts.BufferedCodeHeaderSize + DataTransfer.GetCodeSize(queuedCode.Code); if (_bufferSpace > codeLength && Channels[queuedCode.Code.Channel].BytesBuffered + codeLength <= Settings.MaxBufferSpacePerChannel && DataTransfer.WriteCode(queuedCode.Code)) { _bytesReserved += codeLength; _bufferSpace -= codeLength; queuedCode.BinarySize = codeLength; Channels[queuedCode.Code.Channel].BytesBuffered += codeLength; Channels[queuedCode.Code.Channel].BufferedCodes.Add(queuedCode); Console.WriteLine($"[info] Sent {queuedCode.Code}, remaining space {Settings.MaxBufferSpacePerChannel - Channels[queuedCode.Code.Channel].BytesBuffered} ({_bufferSpace} total), needed {codeLength}"); return(true); } } catch (Exception e) { queuedCode.SetException(e); return(true); } return(false); }
/// <summary> /// Process pending requests on this channel /// </summary> /// <returns>If anything more can be done on this channel</returns> public bool ProcessRequests() { // 1. 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> /// Initialize the SPI interface but do not connect yet /// </summary> public static void Init() => DataTransfer.Init();
/// <summary> /// Perform communication with the RepRapFirmware controller /// </summary> /// <returns>Asynchronous task</returns> public static async Task Run() { do { // Check if an emergency stop has been requested if (_emergencyStopRequested && DataTransfer.WriteEmergencyStop()) { _emergencyStopRequested = false; Console.WriteLine("[info] Emergency stop"); DataTransfer.PerformFullTransfer(); } // Check if a firmware reset has been requested if (_resetRequested && DataTransfer.WriteReset()) { _resetRequested = false; Console.WriteLine("[info] Resetting controller"); DataTransfer.PerformFullTransfer(); } // Check if a firmware update is supposed to be performed if (_iapStream != null && _firmwareStream != null) { await Invalidate("Firmware update imminent"); await PerformFirmwareUpdate(); if (Program.UpdateOnly) { // Stop after installing the firmware update break; } } // Invalidate data if a controller reset has been performed if (DataTransfer.HadReset()) { _emergencyStopRequested = _resetRequested = false; await Invalidate("Controller has been reset"); } // Check for changes of the print status. // The packet providing file info has be sent first because it includes a time_t value that must reside on a 64-bit boundary! if (_printStarted) { using (Model.Provider.AccessReadOnly()) { _printStarted = !DataTransfer.WritePrintStarted(Model.Provider.Get.Job.File); } } else if (_printStoppedReason.HasValue && DataTransfer.WritePrintStopped(_printStoppedReason.Value)) { _printStoppedReason = null; } // Deal with heightmap requests using (await _heightmapLock.LockAsync()) { // Check if the heightmap is supposed to be set if (_setHeightmapRequest != null && DataTransfer.WriteHeightMap(_heightmapToSet)) { _setHeightmapRequest.SetResult(null); _setHeightmapRequest = null; } // Check if the heightmap is requested if (_getHeightmapRequest != null && !_isHeightmapRequested) { _isHeightmapRequested = DataTransfer.WriteGetHeightMap(); } } // Process incoming packets for (int i = 0; i < DataTransfer.PacketsToRead; i++) { PacketHeader?packet; try { packet = DataTransfer.ReadPacket(); if (!packet.HasValue) { Console.WriteLine("[err] Read invalid packet"); break; } await ProcessPacket(packet.Value); } catch (ArgumentOutOfRangeException) { DataTransfer.DumpMalformedPacket(); throw; } } _bytesReserved = 0; // Process pending codes, macro files and requests for resource locks/unlocks as well as flush requests bool dataProcessed; do { dataProcessed = false; foreach (ChannelInformation channel in _channels) { using (channel.Lock()) { if (!channel.IsBlocked) { if (channel.ProcessRequests()) { // Something could be processed dataProcessed = true; } else { // Cannot do any more on this channel channel.IsBlocked = true; } } } } }while (dataProcessed); // Request object model updates if (DateTime.Now - _lastQueryTime > TimeSpan.FromMilliseconds(Settings.ModelUpdateInterval)) { DataTransfer.WriteGetObjectModel(_moduleToQuery); _lastQueryTime = DateTime.Now; } // Update filament assignment per extruder drive lock (_extruderFilamentUpdates) { if (_extruderFilamentUpdates.TryPeek(out Tuple <int, string> filamentMapping) && DataTransfer.WriteAssignFilament(filamentMapping.Item1, filamentMapping.Item2)) { _extruderFilamentUpdates.Dequeue(); } } // Do another full SPI transfer DataTransfer.PerformFullTransfer(); _channels.ResetBlockedChannels(); // Wait a moment await Task.Delay(Settings.SpiPollDelay, Program.CancelSource.Token); }while (!Program.CancelSource.IsCancellationRequested); }
/// <summary> /// Initialize physical transfer and perform initial data transfer. /// This is only called once on initialization /// </summary> /// <returns>Asynchronous task</returns> public static bool Connect() => DataTransfer.PerformFullTransfer(false);
private static async Task PerformFirmwareUpdate() { // Notify clients that we are now installing a firmware update using (await Model.Provider.AccessReadWriteAsync()) { Model.Provider.Get.State.Status = MachineStatus.Updating; } // Get the CRC16 checksum of the firmware binary byte[] firmwareBlob = new byte[_firmwareStream.Length]; await _firmwareStream.ReadAsync(firmwareBlob, 0, (int)_firmwareStream.Length); ushort crc16 = CRC16.Calculate(firmwareBlob); // Send the IAP binary to the firmware Console.Write("[info] Flashing IAP binary"); bool dataSent; do { dataSent = DataTransfer.WriteIapSegment(_iapStream); DataTransfer.PerformFullTransfer(); Console.Write('.'); }while (dataSent); Console.WriteLine(); _iapStream.Close(); _iapStream = null; // Start the IAP binary DataTransfer.StartIap(); // Send the firmware binary to the IAP program int numRetries = 0; do { if (numRetries != 0) { Console.WriteLine("Error"); } Console.Write("[info] Flashing RepRapFirmware"); _firmwareStream.Seek(0, SeekOrigin.Begin); while (DataTransfer.FlashFirmwareSegment(_firmwareStream)) { Console.Write('.'); } Console.WriteLine(); Console.Write("[info] Verifying checksum... "); }while (++numRetries < 3 && !DataTransfer.VerifyFirmwareChecksum(_firmwareStream.Length, crc16)); if (numRetries == 3) { Console.WriteLine("Error"); // Failed to flash the firmware await Utility.Logger.LogOutput(MessageType.Error, "Could not flash the firmware binary after 3 attempts. Please install it manually via bossac."); Program.CancelSource.Cancel(); } else { Console.WriteLine("OK"); // Wait for the IAP binary to restart the controller DataTransfer.WaitForIapReset(); Console.WriteLine("[info] Firmware update successful!"); } _firmwareStream.Close(); _firmwareStream = null; using (_firmwareUpdateLock.Lock()) { _firmwareUpdateRequest.SetResult(null); _firmwareUpdateRequest = null; } }
/// <summary> /// Initialize the SPI interface but do not connect yet /// </summary> public static void Init() { // Initialize SPI and GPIO pin DataTransfer.Initialize(); }
/// <summary> /// Perform communication with the RepRapFirmware controller /// </summary> /// <returns>Asynchronous task</returns> public static async Task Run() { do { // Check if an emergency stop has been requested if (_emergencyStopRequested && DataTransfer.WriteEmergencyStop()) { _emergencyStopRequested = false; Console.WriteLine("[info] Emergency stop"); DataTransfer.PerformFullTransfer(); } // Check if a firmware reset has been requested if (_resetRequested && DataTransfer.WriteReset()) { _resetRequested = false; Console.WriteLine("[info] Resetting controller"); DataTransfer.PerformFullTransfer(); } // Check if a firmware update is supposed to be performed if (_iapStream != null && _firmwareStream != null) { await InvalidateData("Firmware update imminent"); await PerformFirmwareUpdate(); } // Invalidate data if a controller reset has been performed if (DataTransfer.HadReset()) { Console.WriteLine("[info] Controller has been reset"); await InvalidateData("Controller reset"); } // Check for changes of the print status. // The packet providing file info has be sent first because it includes a time_t value that must reside on a 64-bit boundary! if (_printStarted) { using (Model.Provider.AccessReadOnly()) { _printStarted = !DataTransfer.WritePrintStarted(Model.Provider.Get.Job.File); } } else if (_printStoppedReason.HasValue && DataTransfer.WritePrintStopped(_printStoppedReason.Value)) { _printStoppedReason = null; } // Deal with heightmap requests using (_heightmapLock.Lock()) { // Check if the heightmap is supposed to be set if (_heightmapToSet != null && DataTransfer.WriteHeightMap(_heightmapToSet)) { _heightmapToSet = null; _setHeightmapRequest.SetResult(null); _setHeightmapRequest = null; } // Check if the heightmap is requested if (_getHeightmapRequest != null && !_heightmapRequested) { _heightmapRequested = DataTransfer.WriteGetHeightMap(); } } // Process incoming packets DateTime startTime = DateTime.Now; for (int i = 0; i < DataTransfer.PacketsToRead; i++) { Communication.PacketHeader?packet; try { packet = DataTransfer.ReadPacket(); if (!packet.HasValue) { Console.WriteLine("[err] Read invalid packet"); break; } } catch (ArgumentOutOfRangeException) { DataTransfer.DumpMalformedPacket(); throw; } await ProcessPacket(packet.Value); if (DateTime.Now - startTime > TimeSpan.FromMilliseconds(250)) { Console.WriteLine($"WARN: Packet with request {packet?.Request} took longer than 250ms!"); } startTime = DateTime.Now; } _bytesReserved = 0; // Process pending codes, macro files and requests for resource locks/unlocks as well as flush requests bool dataProcessed; List <CodeChannel> blockedChannels = new List <CodeChannel>(); do { dataProcessed = false; foreach (CodeChannel channel in CodeChannels) { if (!blockedChannels.Contains(channel)) { using (Channels[channel].Lock()) { if (Channels[channel].ProcessRequests()) { // Something could be done on this channel dataProcessed = true; } else { // Don't call Process() again for this channel if it returned false before blockedChannels.Add(channel); } } } } } while (dataProcessed); if (DateTime.Now - startTime > TimeSpan.FromMilliseconds(250)) { Console.WriteLine("WARN: Channel processing took longer than 250ms!"); } startTime = DateTime.Now; // Request object model updates if (IsIdle || DateTime.Now - _lastQueryTime > TimeSpan.FromMilliseconds(Settings.MaxUpdateDelay)) { DataTransfer.WriteGetObjectModel(_moduleToQuery); _lastQueryTime = DateTime.Now; } if (DateTime.Now - startTime > TimeSpan.FromMilliseconds(250)) { Console.WriteLine("WARN: Object model processing took longer than 250ms!"); } startTime = DateTime.Now; // Do another full SPI transfer DataTransfer.PerformFullTransfer(); if (DateTime.Now - startTime > TimeSpan.FromMilliseconds(250)) { Console.WriteLine("WARN: SPI transfer took longer than 250ms!"); } // Wait a moment if (IsIdle) { await Task.Delay(Settings.SpiPollDelay, Program.CancelSource.Token); } } while (!Program.CancelSource.IsCancellationRequested); }
/// <summary> /// Perform communication with the RepRapFirmware controller /// </summary> /// <returns>Asynchronous task</returns> public static async Task Run() { if (Settings.NoSpiTask) { await Task.Delay(-1, Program.CancellationToken); return; } do { using (await _firmwareActionLock.LockAsync(Program.CancellationToken)) { // Check if an emergency stop has been requested if (_firmwareHaltRequest != null && DataTransfer.WriteEmergencyStop()) { _logger.Warn("Emergency stop"); DataTransfer.PerformFullTransfer(); _firmwareHaltRequest.SetResult(null); _firmwareHaltRequest = null; } // Check if a firmware reset has been requested if (_firmwareResetRequest != null && DataTransfer.WriteReset()) { _logger.Warn("Resetting controller"); DataTransfer.PerformFullTransfer(); _firmwareResetRequest.SetResult(null); _firmwareResetRequest = null; if (!Settings.NoTerminateOnReset) { // Wait for the program to terminate and don't perform any extra transfers await Task.Delay(-1, Program.CancellationToken); } } } // Check if a firmware update is supposed to be performed using (await _firmwareUpdateLock.LockAsync(Program.CancellationToken)) { if (_iapStream != null && _firmwareStream != null) { await Invalidate("Firmware update imminent"); try { await PerformFirmwareUpdate(); _firmwareUpdateRequest?.SetResult(null); _firmwareUpdateRequest = null; } catch (Exception e) { _firmwareUpdateRequest?.SetException(e); _firmwareUpdateRequest = null; if (e is OperationCanceledException) { _logger.Debug(e, "Firmware update cancelled"); } else { throw; } } _iapStream = _firmwareStream = null; } } // Invalidate data if a controller reset has been performed if (DataTransfer.HadReset()) { await Invalidate("Controller has been reset"); } // Check for changes of the print status. // The packet providing file info has be sent first because it includes a time_t value that must reside on a 64-bit boundary! if (_printStarted) { using (await Model.Provider.AccessReadOnlyAsync()) { _printStarted = !DataTransfer.WritePrintStarted(Model.Provider.Get.Job.File); } } else { using (await _printStopppedReasonLock.LockAsync(Program.CancellationToken)) { if (_printStoppedReason != null && DataTransfer.WritePrintStopped(_printStoppedReason.Value)) { _printStoppedReason = null; } } } // Deal with heightmap requests using (await _heightmapLock.LockAsync(Program.CancellationToken)) { // Check if the heightmap is supposed to be set if (_setHeightmapRequest != null && DataTransfer.WriteHeightMap(_heightmapToSet)) { _setHeightmapRequest.SetResult(null); _setHeightmapRequest = null; } // Check if the heightmap is requested if (_getHeightmapRequest != null && !_isHeightmapRequested) { _isHeightmapRequested = DataTransfer.WriteGetHeightMap(); } } // Process incoming packets for (int i = 0; i < DataTransfer.PacketsToRead; i++) { PacketHeader?packet; try { packet = DataTransfer.ReadPacket(); if (packet == null) { _logger.Error("Read invalid packet"); break; } await ProcessPacket(packet.Value); } catch (ArgumentOutOfRangeException) { DataTransfer.DumpMalformedPacket(); throw; } } _bytesReserved = 0; // Process pending codes, macro files and requests for resource locks/unlocks as well as flush requests await _channels.Run(); // Request object model updates if (DateTime.Now - _lastQueryTime > TimeSpan.FromMilliseconds(Settings.ModelUpdateInterval)) { if (DataTransfer.ProtocolVersion == 1) { using (await Model.Provider.AccessReadOnlyAsync()) { if (Model.Provider.Get.Boards.Count == 0 && DataTransfer.WriteGetLegacyConfigResponse()) { // We no longer support regular status responses except to obtain the board name for updating the firmware _lastQueryTime = DateTime.Now; } } } else { bool objectModelQueried = false; lock (_pendingModelQueries) { // Query specific object model values on demand if (_pendingModelQueries.TryPeek(out Tuple <string, string> modelQuery) && DataTransfer.WriteGetObjectModel(modelQuery.Item1, modelQuery.Item2)) { objectModelQueried = true; _pendingModelQueries.Dequeue(); } } if (!objectModelQueried && DataTransfer.WriteGetObjectModel(string.Empty, "d99fn")) { // Query live values in regular intervals _lastQueryTime = DateTime.Now; } } } // Ask for expressions to be evaluated lock (_evaluateExpressionRequests) { int numEvaluationsSent = 0; foreach (EvaluateExpressionRequest request in _evaluateExpressionRequests) { if (!request.Written) { if (DataTransfer.WriteEvaluateExpression(request.Channel, request.Expression)) { request.Written = true; numEvaluationsSent++; if (numEvaluationsSent >= Consts.MaxEvaluationRequestsPerTransfer) { break; } } } } } // Send pending messages lock (_messagesToSend) { while (_messagesToSend.TryPeek(out Tuple <MessageTypeFlags, string> message)) { if (DataTransfer.WriteMessage(message.Item1, message.Item2)) { _messagesToSend.Dequeue(); } else { break; } } } // Update filament assignment per extruder drive. This must happen when config.g has finished or M701 is requested if (!Macro.RunningConfig || _assignFilaments) { lock (_extruderFilamentUpdates) { while (_extruderFilamentUpdates.TryPeek(out Tuple <int, string> filamentMapping)) { if (DataTransfer.WriteAssignFilament(filamentMapping.Item1, filamentMapping.Item2)) { _extruderFilamentUpdates.Dequeue(); } else { break; } } } _assignFilaments = false; } // Do another full SPI transfe DataTransfer.PerformFullTransfer(); _channels.ResetBlockedChannels(); // Wait a moment unless instructions are being sent rapidly to RRF bool skipDelay; using (await Model.Provider.AccessReadOnlyAsync()) { skipDelay = Model.Provider.Get.State.Status == MachineStatus.Updating; } if (!skipDelay) { await Task.Delay(Settings.SpiPollDelay, Program.CancellationToken); } }while (true); }
/// <summary> /// Perform the firmware update internally /// </summary> /// <returns>Asynchronous task</returns> private static async Task PerformFirmwareUpdate() { using (await Model.Provider.AccessReadWriteAsync()) { Model.Provider.Get.State.Status = MachineStatus.Updating; } DataTransfer.Updating = true; try { // Get the CRC16 checksum of the firmware binary byte[] firmwareBlob = new byte[_firmwareStream.Length]; await _firmwareStream.ReadAsync(firmwareBlob, 0, (int)_firmwareStream.Length); ushort crc16 = CRC16.Calculate(firmwareBlob); // Send the IAP binary to the firmware _logger.Info("Flashing IAP binary"); bool dataSent; do { dataSent = DataTransfer.WriteIapSegment(_iapStream); DataTransfer.PerformFullTransfer(); if (_logger.IsDebugEnabled) { Console.Write('.'); } }while (dataSent); if (_logger.IsDebugEnabled) { Console.WriteLine(); } // Start the IAP binary DataTransfer.StartIap(); // Send the firmware binary to the IAP program int numRetries = 0; do { if (numRetries != 0) { _logger.Error("Firmware checksum verification failed"); } _logger.Info("Flashing RepRapFirmware"); _firmwareStream.Seek(0, SeekOrigin.Begin); while (DataTransfer.FlashFirmwareSegment(_firmwareStream)) { if (_logger.IsDebugEnabled) { Console.Write('.'); } } if (_logger.IsDebugEnabled) { Console.WriteLine(); } _logger.Info("Verifying checksum"); }while (++numRetries < 3 && !DataTransfer.VerifyFirmwareChecksum(_firmwareStream.Length, crc16)); if (numRetries == 3) { // Failed to flash the firmware await Logger.LogOutput(MessageType.Error, "Could not flash the firmware binary after 3 attempts. Please install it manually via bossac."); Program.CancelSource.Cancel(); } else { // Wait for the IAP binary to restart the controller await DataTransfer.WaitForIapReset(); _logger.Info("Firmware update successful"); } } finally { DataTransfer.Updating = false; // Machine state is reset when the next status response is processed } }
/// <summary> /// Process pending requests on this channel /// </summary> /// <returns>If anything more can be done on this channel</returns> public bool ProcessRequests() { // 1. Priority codes if (PriorityCodes.TryPeek(out QueuedCode queuedCode)) { if (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode))) { PriorityCodes.Dequeue(); return(true); } // Block this channel until every priority code is gone IsBlocked = true; } // 2. Suspended codes being resumed (may include suspended codes from nested macros) if (_resumingBuffer) { ResumeBuffer(); return(_resumingBuffer); } // FIXME This doesn't work yet for non-M292 codes. Needs more refactoring if (WaitingForMessageAcknowledgement) { // Still waiting for M292... return(false); } // 3. Macro codes if (NestedMacroCodes.TryPeek(out queuedCode) && (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode)))) { NestedMacroCodes.Dequeue(); return(true); } // 4. New codes from macro files if (NestedMacros.TryPeek(out MacroFile macroFile)) { // Try to read the next real code from the system macro being executed Commands.Code code = null; if (!macroFile.IsFinished && NestedMacroCodes.Count < Settings.BufferedMacroCodes) { code = macroFile.ReadCode(); } // If there is any, start executing it in the background. An interceptor may also generate extra codes if (code != null) { // Note that the following code is executed asynchronously to avoid potential // deadlocks which would occur when SPI data is awaited (e.g. heightmap queries) queuedCode = new QueuedCode(code); NestedMacroCodes.Enqueue(queuedCode); _ = Task.Run(async() => { try { CodeResult result = await code.Execute(); if (!queuedCode.IsReadyToSend) { // Macro codes need special treatment because they may complete before they are actually sent to RepRapFirmware queuedCode.HandleReply(result); } if (!macroFile.IsAborted) { await Utility.Logger.LogOutput(result); } } catch (OperationCanceledException) { // Something has gone wrong and the SPI connector has invalidated everything - don't deal with this (yet?) } catch (AggregateException ae) { // FIXME: Should this terminate the macro being executed? Console.WriteLine($"[err] {code} -> {ae.InnerException.Message}"); } }); return(true); } // Macro file is complete if no more codes can be read from the file and the buffered codes are completely gone if (macroFile.IsFinished && !NestedMacroCodes.TryPeek(out _) && BufferedCodes.Count == 0 && ((macroFile.StartCode != null && macroFile.StartCode.DoingNestedMacro) || (macroFile.StartCode == null && !SystemMacroHasFinished)) && MacroCompleted(macroFile.StartCode, macroFile.IsAborted)) { if (macroFile.StartCode == null) { SystemMacroHasFinished = true; } Console.WriteLine($"[info] {(macroFile.IsAborted ? "Aborted" : "Finished")} macro file '{Path.GetFileName(macroFile.FileName)}'"); return(false); } } // 5. Regular codes - only applicable if no macro is being executed else if (PendingCodes.TryPeek(out queuedCode) && BufferCode(queuedCode)) { PendingCodes.Dequeue(); return(true); } // 6. Lock/Unlock requests if (BufferedCodes.Count == 0 && PendingLockRequests.TryPeek(out QueuedLockRequest lockRequest)) { if (lockRequest.IsLockRequest) { if (!lockRequest.IsLockRequested) { lockRequest.IsLockRequested = DataTransfer.WriteLockMovementAndWaitForStandstill(Channel); } } else if (DataTransfer.WriteUnlock(Channel)) { lockRequest.Resolve(true); PendingLockRequests.Dequeue(); } return(false); } // 7. Flush requests if (BufferedCodes.Count == 0 && PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> source)) { source.SetResult(true); return(false); } return(false); }
/// <summary> /// Initialize the SPI interface but do not connect yet /// </summary> public static void Init() { DataTransfer.Init(); Program.CancelSource.Token.Register(() => _ = Invalidate(null)); }
/// <summary> /// Initialize physical transfer and perform initial data transfer. /// This is only called once on initialization /// </summary> /// <returns>Asynchronous task</returns> public static Task <bool> Connect() => DataTransfer.PerformFullTransfer(false);