/// <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
            }
        }
Beispiel #12
0
        /// <summary>
        /// Process pending requests on this channel
        /// </summary>
        /// <returns>If anything more can be done on this channel</returns>
        public bool ProcessRequests()
        {
            // 1. Priority codes
            if (PriorityCodes.TryPeek(out QueuedCode queuedCode))
            {
                if (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode)))
                {
                    PriorityCodes.Dequeue();
                    return(true);
                }

                // Block this channel until every priority code is gone
                IsBlocked = true;
            }

            // 2. Suspended codes being resumed (may include suspended codes from nested macros)
            if (_resumingBuffer)
            {
                ResumeBuffer();
                return(_resumingBuffer);
            }

            // FIXME This doesn't work yet for non-M292 codes. Needs more refactoring
            if (WaitingForMessageAcknowledgement)
            {
                // Still waiting for M292...
                return(false);
            }

            // 3. Macro codes
            if (NestedMacroCodes.TryPeek(out queuedCode) && (queuedCode.IsFinished || (queuedCode.IsReadyToSend && BufferCode(queuedCode))))
            {
                NestedMacroCodes.Dequeue();
                return(true);
            }

            // 4. New codes from macro files
            if (NestedMacros.TryPeek(out MacroFile macroFile))
            {
                // Try to read the next real code from the system macro being executed
                Commands.Code code = null;
                if (!macroFile.IsFinished && NestedMacroCodes.Count < Settings.BufferedMacroCodes)
                {
                    code = macroFile.ReadCode();
                }

                // If there is any, start executing it in the background. An interceptor may also generate extra codes
                if (code != null)
                {
                    // Note that the following code is executed asynchronously to avoid potential
                    // deadlocks which would occur when SPI data is awaited (e.g. heightmap queries)
                    queuedCode = new QueuedCode(code);
                    NestedMacroCodes.Enqueue(queuedCode);
                    _ = Task.Run(async() =>
                    {
                        try
                        {
                            CodeResult result = await code.Execute();
                            if (!queuedCode.IsReadyToSend)
                            {
                                // Macro codes need special treatment because they may complete before they are actually sent to RepRapFirmware
                                queuedCode.HandleReply(result);
                            }
                            if (!macroFile.IsAborted)
                            {
                                await Utility.Logger.LogOutput(result);
                            }
                        }
                        catch (OperationCanceledException)
                        {
                            // Something has gone wrong and the SPI connector has invalidated everything - don't deal with this (yet?)
                        }
                        catch (AggregateException ae)
                        {
                            // FIXME: Should this terminate the macro being executed?
                            Console.WriteLine($"[err] {code} -> {ae.InnerException.Message}");
                        }
                    });

                    return(true);
                }

                // Macro file is complete if no more codes can be read from the file and the buffered codes are completely gone
                if (macroFile.IsFinished && !NestedMacroCodes.TryPeek(out _) && BufferedCodes.Count == 0 &&
                    ((macroFile.StartCode != null && macroFile.StartCode.DoingNestedMacro) || (macroFile.StartCode == null && !SystemMacroHasFinished)) &&
                    MacroCompleted(macroFile.StartCode, macroFile.IsAborted))
                {
                    if (macroFile.StartCode == null)
                    {
                        SystemMacroHasFinished = true;
                    }
                    Console.WriteLine($"[info] {(macroFile.IsAborted ? "Aborted" : "Finished")} macro file '{Path.GetFileName(macroFile.FileName)}'");
                    return(false);
                }
            }

            // 5. Regular codes - only applicable if no macro is being executed
            else if (PendingCodes.TryPeek(out queuedCode) && BufferCode(queuedCode))
            {
                PendingCodes.Dequeue();
                return(true);
            }

            // 6. Lock/Unlock requests
            if (BufferedCodes.Count == 0 && PendingLockRequests.TryPeek(out QueuedLockRequest lockRequest))
            {
                if (lockRequest.IsLockRequest)
                {
                    if (!lockRequest.IsLockRequested)
                    {
                        lockRequest.IsLockRequested = DataTransfer.WriteLockMovementAndWaitForStandstill(Channel);
                    }
                }
                else if (DataTransfer.WriteUnlock(Channel))
                {
                    lockRequest.Resolve(true);
                    PendingLockRequests.Dequeue();
                }
                return(false);
            }

            // 7. Flush requests
            if (BufferedCodes.Count == 0 && PendingFlushRequests.TryDequeue(out TaskCompletionSource <bool> source))
            {
                source.SetResult(true);
                return(false);
            }

            return(false);
        }
 /// <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);