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