/// <summary>
        /// Process a print status response (type 3)
        /// </summary>
        /// <param name="json">JSON response in UTF-8 format</param>
        /// <returns>Asynchronous task</returns>
        private static async Task ProcessPrintResponse(ReadOnlyMemory <byte> json)
        {
            PrintStatusResponse printResponse = (PrintStatusResponse)JsonSerializer.Deserialize(json.Span, typeof(PrintStatusResponse), JsonHelper.DefaultJsonOptions);

            using (await Provider.AccessReadWriteAsync())
            {
                // Check if the last layer is complete
                if (printResponse.currentLayer > Provider.Get.Job.Layers.Count + 1)
                {
                    float   lastHeight = 0F, lastDuration = 0F, lastProgress = 0F;
                    float[] lastFilamentUsage = new float[printResponse.extrRaw.Count];
                    foreach (Layer l in Provider.Get.Job.Layers)
                    {
                        lastHeight   += l.Height;
                        lastDuration += l.Duration;
                        lastProgress += l.FractionPrinted;
                        for (int i = 0; i < Math.Min(lastFilamentUsage.Length, l.Filament.Count); i++)
                        {
                            lastFilamentUsage[i] += l.Filament[i];
                        }
                    }

                    float[] filamentUsage = new float[printResponse.extrRaw.Count];
                    for (int i = 0; i < filamentUsage.Length; i++)
                    {
                        filamentUsage[i] = printResponse.extrRaw[i] - lastFilamentUsage[i];
                    }

                    float printDuration = printResponse.printDuration - printResponse.warmUpDuration;
                    Layer layer         = new Layer
                    {
                        Duration        = printDuration - lastDuration,
                        Filament        = new List <float>(filamentUsage),
                        FractionPrinted = (printResponse.fractionPrinted / 100F) - lastProgress,
                        Height          = (printResponse.currentLayer > 2) ? _currentHeight - lastHeight : printResponse.firstLayerHeight
                    };
                    Provider.Get.Job.Layers.Add(layer);

                    // FIXME: In case Z isn't mapped to the 3rd axis...
                    _currentHeight = printResponse.coords.xyz[2];
                }
                else if (printResponse.currentLayer < Provider.Get.Job.Layers.Count && GetStatus(printResponse.status) == MachineStatus.Processing)
                {
                    // Starting a new print job
                    Provider.Get.Job.Layers.Clear();
                    _currentHeight = 0F;
                }

                Provider.Get.Job.Layer        = printResponse.currentLayer;
                Provider.Get.Job.LayerTime    = (printResponse.currentLayer == 1) ? printResponse.firstLayerDuration : printResponse.currentLayerTime;
                Provider.Get.Job.FilePosition = printResponse.filePosition;
                ListHelpers.SetList(Provider.Get.Job.ExtrudedRaw, printResponse.extrRaw);
                Provider.Get.Job.Duration           = printResponse.printDuration;
                Provider.Get.Job.WarmUpDuration     = printResponse.warmUpDuration;
                Provider.Get.Job.TimesLeft.File     = (printResponse.timesLeft.file > 0F) ? (float?)printResponse.timesLeft.file : null;
                Provider.Get.Job.TimesLeft.Filament = (printResponse.timesLeft.filament > 0F) ? (float?)printResponse.timesLeft.filament : null;
                Provider.Get.Job.TimesLeft.Layer    = (printResponse.timesLeft.layer > 0F) ? (float?)printResponse.timesLeft.layer : null;
            }
        }
        private static async Task DoMerge(byte module, string json)
        {
            if (module == 2)
            {
                // Deserialize extended response (temporary)
                var response = new
                {
                    status = 'I',
                    coords = new
                    {
                        axesHomed = new byte[0],
                        xyz       = new float[0],
                        machine   = new float[0],
                        extr      = new float[0]
                    },
                    speeds = new
                    {
                        requested = 0.0F,
                        top       = 0.0F
                    },
                    currentTool = -1,
                    output      = new
                    {
                        beepDuration  = 0,
                        beepFrequency = 0,
                        message       = "",
                        msgBox        = new
                        {
                            msg      = "",
                            title    = "",
                            seq      = 0,
                            timeout  = 0,
                            controls = 0
                        }
                    },
                    Params = new
                    {
                        atxPower    = 0,
                        fanPercent  = new float[0],
                        fanNames    = new string[0],
                        speedFactor = 0.0F,
                        extrFactors = new float[0],
                        babystep    = 0.0F
                    },
                    sensors = new
                    {
                        probeValue     = 0,
                        probeSecondary = new int[0],
                        fanRPM         = new int[0]
                    },
                    temps = new
                    {
                        bed = new
                        {
                            active  = 0.0F,
                            standby = 0.0F,
                            state   = 0,
                            heater  = 0
                        },
                        chamber = new
                        {
                            active  = 0.0F,
                            standby = 0.0F,
                            state   = 0,
                            heater  = 0
                        },
                        current = new float[0],
                        state   = new byte[0],
                        names   = new string[0],
                        tools   = new
                        {
                            active  = new[] { new float[0] },
                            standby = new[] { new float[0] }
                        },
                        extra = new[]
                        {
                            new
                            {
                                name = "",
                                temp = 0.0F
                            }
                        }
                    },
                    coldExtrudeTemp  = 160.0F,
                    coldRetractTemp  = 90.0F,
                    compensation     = "",
                    controllableFans = 0,
                    tempLimit        = 0.0F,
                    tools            = new[]
                    {
                        new
                        {
                            number    = 0,
                            name      = "",
                            heaters   = new int[0],
                            extruders = new int[0],
                            fan       = 0,
                            filament  = "",
                            offsets   = new float[0]
                        }
                    },
                    mcutemp = new
                    {
                        min = 0.0F,
                        cur = 0.0F,
                        max = 0.0F
                    },
                    vin = new
                    {
                        min = 0.0F,
                        cur = 0.0F,
                        max = 0.0F
                    },
                    firmwareName = "",
                    mode         = "FFF"
                };
                response = JsonConvert.DeserializeAnonymousType(json, response);

                using (await Provider.AccessReadWriteAsync())
                {
                    // - Electronics -
                    Provider.Get.Electronics.Firmware.Name   = response.firmwareName;
                    Provider.Get.Electronics.McuTemp.Current = response.mcutemp.cur;
                    Provider.Get.Electronics.McuTemp.Min     = response.mcutemp.min;
                    Provider.Get.Electronics.McuTemp.Max     = response.mcutemp.max;
                    Provider.Get.Electronics.VIn.Current     = response.vin.cur;
                    Provider.Get.Electronics.VIn.Min         = response.vin.min;
                    Provider.Get.Electronics.VIn.Max         = response.vin.max;

                    // - Fans -
                    for (int fan = 0; fan < response.Params.fanPercent.Length; fan++)
                    {
                        Fan fanObj;
                        if (fan >= Provider.Get.Fans.Count)
                        {
                            fanObj = new Fan();
                            Provider.Get.Fans.Add(fanObj);
                        }
                        else
                        {
                            fanObj = Provider.Get.Fans[fan];
                        }

                        fanObj.Name  = response.Params.fanNames[fan];
                        fanObj.Rpm   = (response.sensors.fanRPM.Length > fan && response.sensors.fanRPM[fan] > 0) ? (int?)response.sensors.fanRPM[fan] : null;
                        fanObj.Value = response.Params.fanPercent[fan] / 100;
                    }
                    for (int fan = Provider.Get.Fans.Count; fan > response.Params.fanPercent.Length; fan--)
                    {
                        Provider.Get.Fans.RemoveAt(fan - 1);
                    }

                    // - Move -
                    Provider.Get.Move.Compensation = response.compensation;
                    Provider.Get.Move.CurrentMove.RequestedSpeed = response.speeds.requested;
                    Provider.Get.Move.CurrentMove.TopSpeed       = response.speeds.top;
                    Provider.Get.Move.SpeedFactor = response.Params.speedFactor / 100;
                    Provider.Get.Move.BabystepZ   = response.Params.babystep;

                    // Update drives
                    int numDrives = response.coords.xyz.Length + response.coords.extr.Length;
                    for (int drive = 0; drive < numDrives; drive++)
                    {
                        Drive driveObj;
                        if (drive >= Provider.Get.Move.Drives.Count)
                        {
                            driveObj = new Drive();
                            Provider.Get.Move.Drives.Add(driveObj);
                        }
                        else
                        {
                            driveObj = Provider.Get.Move.Drives[drive];
                        }

                        driveObj.Position = (drive < response.coords.xyz.Length) ? response.coords.xyz[drive] : response.coords.extr[drive - response.coords.xyz.Length];
                    }
                    for (int drive = Provider.Get.Move.Drives.Count; drive > numDrives; drive--)
                    {
                        Provider.Get.Move.Drives.RemoveAt(drive - 1);
                    }

                    // Update axes
                    for (int axis = 0; axis < response.coords.xyz.Length; axis++)
                    {
                        Axis axisObj;
                        if (axis >= Provider.Get.Move.Axes.Count)
                        {
                            axisObj = new Axis();
                            Provider.Get.Move.Axes.Add(axisObj);
                        }
                        else
                        {
                            axisObj = Provider.Get.Move.Axes[axis];
                        }

                        axisObj.Letter          = GetAxisLetter(axis);
                        axisObj.Homed           = response.coords.axesHomed[axis] != 0;
                        axisObj.MachinePosition = response.coords.machine[axis];
                    }
                    for (int axis = Provider.Get.Move.Axes.Count; axis > response.coords.xyz.Length; axis--)
                    {
                        Provider.Get.Move.Axes.RemoveAt(axis - 1);
                    }

                    // Update extruder drives
                    Extruder extruderObj;
                    for (int extruder = 0; extruder < response.coords.extr.Length; extruder++)
                    {
                        if (extruder >= Provider.Get.Move.Extruders.Count)
                        {
                            extruderObj = new Extruder();
                            Provider.Get.Move.Extruders.Add(extruderObj);
                        }
                        else
                        {
                            extruderObj = Provider.Get.Move.Extruders[extruder];
                        }

                        extruderObj.Factor = response.Params.extrFactors[extruder] / 100;
                        if (extruderObj.Drives.Count == 1)
                        {
                            extruderObj.Drives[0] = response.coords.xyz.Length + extruder;
                        }
                        else
                        {
                            extruderObj.Drives.Add(response.coords.xyz.Length + extruder);
                        }
                    }
                    for (int extruder = Provider.Get.Move.Extruders.Count; extruder > response.coords.extr.Length; extruder--)
                    {
                        Provider.Get.Move.Extruders.RemoveAt(extruder - 1);
                    }

                    // - Heat -
                    Provider.Get.Heat.ColdExtrudeTemperature = response.coldExtrudeTemp;
                    Provider.Get.Heat.ColdRetractTemperature = response.coldRetractTemp;

                    // Update heaters
                    for (int heater = 0; heater < response.temps.current.Length; heater++)
                    {
                        Heater heaterObj;
                        if (heater >= Provider.Get.Heat.Heaters.Count)
                        {
                            heaterObj = new Heater();
                            Provider.Get.Heat.Heaters.Add(heaterObj);
                        }
                        else
                        {
                            heaterObj = Provider.Get.Heat.Heaters[heater];
                        }

                        heaterObj.Current = response.temps.current[heater];
                        heaterObj.Max     = response.tempLimit;
                        heaterObj.Name    = response.temps.names[heater];
                        heaterObj.Sensor  = heater;
                        heaterObj.State   = (HeaterState)response.temps.state[heater];
                    }
                    for (int heater = Provider.Get.Heat.Heaters.Count; heater > response.temps.current.Length; heater--)
                    {
                        Provider.Get.Heat.Heaters.RemoveAt(heater - 1);
                    }

                    // Update beds
                    if (response.temps.bed != null)
                    {
                        BedOrChamber bedObj;
                        if (Provider.Get.Heat.Beds.Count == 0)
                        {
                            bedObj = new BedOrChamber();
                            Provider.Get.Heat.Beds.Add(bedObj);
                        }
                        else
                        {
                            bedObj = Provider.Get.Heat.Beds[0];
                        }

                        if (bedObj.Heaters.Count == 0)
                        {
                            bedObj.Active.Add(response.temps.bed.active);
                            bedObj.Standby.Add(response.temps.bed.standby);
                            bedObj.Heaters.Add(response.temps.bed.heater);
                        }
                        else
                        {
                            bedObj.Active[0]  = response.temps.bed.active;
                            bedObj.Standby[0] = response.temps.bed.standby;
                            bedObj.Heaters[0] = response.temps.bed.heater;
                        }
                    }
                    else if (Provider.Get.Heat.Beds.Count > 0)
                    {
                        Provider.Get.Heat.Beds.Clear();
                    }

                    // Update chambers
                    if (response.temps.chamber != null)
                    {
                        BedOrChamber chamberObj;
                        if (Provider.Get.Heat.Chambers.Count == 0)
                        {
                            chamberObj = new BedOrChamber();
                            Provider.Get.Heat.Chambers.Add(chamberObj);
                        }
                        else
                        {
                            chamberObj = Provider.Get.Heat.Chambers[0];
                        }

                        if (chamberObj.Heaters.Count == 0)
                        {
                            chamberObj.Active.Add(response.temps.chamber.active);
                            chamberObj.Standby.Add(response.temps.chamber.standby);
                            chamberObj.Heaters.Add(response.temps.chamber.heater);
                        }
                        else
                        {
                            chamberObj.Active[0]  = response.temps.chamber.active;
                            chamberObj.Standby[0] = response.temps.chamber.standby;
                            chamberObj.Heaters[0] = response.temps.chamber.heater;
                        }
                    }
                    else if (Provider.Get.Heat.Chambers.Count > 0)
                    {
                        Provider.Get.Heat.Chambers.Clear();
                    }

                    // Update extra heaters
                    for (int extra = 0; extra < response.temps.extra.Length; extra++)
                    {
                        ExtraHeater extraObj;
                        if (extra >= Provider.Get.Heat.Extra.Count)
                        {
                            extraObj = new ExtraHeater();
                            Provider.Get.Heat.Extra.Add(extraObj);
                        }
                        else
                        {
                            extraObj = Provider.Get.Heat.Extra[extra];
                        }

                        extraObj.Current = response.temps.extra[extra].temp;
                        extraObj.Name    = response.temps.extra[extra].name;
                    }
                    for (int extra = Provider.Get.Heat.Extra.Count; extra > response.temps.extra.Length; extra--)
                    {
                        Provider.Get.Heat.Extra.RemoveAt(extra - 1);
                    }

                    // - Sensors -
                    Probe probeObj;
                    if (Provider.Get.Sensors.Probes.Count == 0)
                    {
                        probeObj = new Probe();
                        Provider.Get.Sensors.Probes.Add(probeObj);
                    }
                    else
                    {
                        probeObj = Provider.Get.Sensors.Probes[0];
                    }

                    probeObj.Value = response.sensors.probeValue;
                    if (response.sensors.probeSecondary != null)
                    {
                        ListHelpers.SetList(probeObj.SecondaryValues, response.sensors.probeSecondary);
                    }

                    // - State -
                    Provider.Get.State.AtxPower = (response.Params.atxPower == -1) ? null : (bool?)(response.Params.atxPower != 0);
                    if (response.output != null)
                    {
                        if (response.output.beepFrequency != 0 && response.output.beepDuration != 0)
                        {
                            Provider.Get.State.Beep.Frequency = response.output.beepFrequency;
                            Provider.Get.State.Beep.Duration  = response.output.beepDuration;
                            _ = Task.Run(async() =>
                            {
                                await Task.Delay(response.output.beepDuration);
                                using (await Provider.AccessReadWriteAsync())
                                {
                                    Provider.Get.State.Beep.Duration  = 0;
                                    Provider.Get.State.Beep.Frequency = 0;
                                }
                            }, Program.CancelSource.Token);
                        }
                        Provider.Get.State.DisplayMessage = response.output.message;
                    }
                    Provider.Get.State.CurrentTool = response.currentTool;
                    Provider.Get.State.Status      = GetStatus(response.status);
                    if (Provider.Get.State.Status == MachineStatus.Idle && FileExecution.MacroFile.DoingMacroFile)
                    {
                        // RRF does not always know whether a macro file is being executed
                        Provider.Get.State.Status = MachineStatus.Busy;
                    }
                    Provider.Get.State.Mode = (MachineMode)Enum.Parse(typeof(MachineMode), response.mode, true);

                    // - Tools -
                    Tool toolObj;
                    for (int tool = 0; tool < response.tools.Length; tool++)
                    {
                        if (tool >= Provider.Get.Tools.Count)
                        {
                            toolObj = new Tool();
                            Provider.Get.Tools.Add(toolObj);
                        }
                        else
                        {
                            toolObj = Provider.Get.Tools[tool];
                        }

                        toolObj.Filament = response.tools[tool].filament;
                        toolObj.Name     = (response.tools[tool].name == "") ? null : response.tools[tool].name;
                        toolObj.Number   = response.tools[tool].number;
                        ListHelpers.SetList(toolObj.Heaters, response.tools[tool].heaters);
                        ListHelpers.SetList(toolObj.Active, response.temps.tools.active[tool]);
                        ListHelpers.SetList(toolObj.Standby, response.temps.tools.standby[tool]);
                        if (toolObj.Fans.Count == 0)
                        {
                            toolObj.Fans.Add(response.tools[tool].fan);
                        }
                        else
                        {
                            toolObj.Fans[0] = response.tools[tool].fan;
                        }
                        ListHelpers.SetList(toolObj.Offsets, response.tools[tool].offsets);
                    }
                    for (int tool = Provider.Get.Tools.Count; tool > response.tools.Length; tool--)
                    {
                        Provider.Get.Tools.RemoveAt(tool - 1);
                    }
                }
            }

            // Deserialize print status response
            else if (module == 3)
            {
                var printResponse = new
                {
                    currentLayer       = 0,
                    currentLayerTime   = 0F,
                    filePosition       = 0L,
                    firstLayerDuration = 0F,
                    extrRaw            = new float[0],
                    printDuration      = 0F,
                    warmUpDuration     = 0F,
                    timesLeft          = new
                    {
                        file     = 0F,
                        filament = 0F,
                        layer    = 0F
                    }
                };
                printResponse = JsonConvert.DeserializeAnonymousType(json, printResponse);

                using (await Provider.AccessReadWriteAsync())
                {
                    Provider.Get.Job.Layer        = printResponse.currentLayer;
                    Provider.Get.Job.LayerTime    = (printResponse.currentLayer == 1) ? printResponse.firstLayerDuration : printResponse.currentLayerTime;
                    Provider.Get.Job.FilePosition = printResponse.filePosition;
                    ListHelpers.SetList(Provider.Get.Job.ExtrudedRaw, printResponse.extrRaw);
                    Provider.Get.Job.Duration           = printResponse.printDuration;
                    Provider.Get.Job.WarmUpDuration     = printResponse.warmUpDuration;
                    Provider.Get.Job.TimesLeft.File     = (printResponse.timesLeft.file > 0F) ? (float?)printResponse.timesLeft.file : null;
                    Provider.Get.Job.TimesLeft.Filament = (printResponse.timesLeft.filament > 0F) ? (float?)printResponse.timesLeft.filament : null;
                    Provider.Get.Job.TimesLeft.Layer    = (printResponse.timesLeft.layer > 0F) ? (float?)printResponse.timesLeft.layer : null;
                }
            }

            // Reset everything if the controller is halted
            using (await Provider.AccessReadOnlyAsync())
            {
                if (Provider.Get.State.Status == MachineStatus.Halted)
                {
                    await SPI.Interface.InvalidateData("Firmware halted");
                }
            }

            // Notify subscribers
            IPC.Processors.Subscription.ModelUpdated();

            // Notify waiting threads about the last module updated
            _moduleUpdateEvents[module].Set();
            _moduleUpdateEvents[module].Reset();
            lock (_moduleUpdateEvents)
            {
                _lastUpdatedModule = module;
            }

            // Force manual garbage collection (maybe the GC cannot keep up with the speed of the update loop)
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
        /// <summary>
        /// Process an advanced status reponse (type 2)
        /// </summary>
        /// <param name="json">JSON response in UTF-8 format</param>
        /// <returns>Asynchronous task</returns>
        private static async Task ProcessAdvancedResponse(ReadOnlyMemory <byte> json)
        {
            AdvancedStatusResponse response = (AdvancedStatusResponse)JsonSerializer.Deserialize(json.Span, typeof(AdvancedStatusResponse), JsonHelper.DefaultJsonOptions);

            List <Tool> addedTools = new List <Tool>();

            using (await Provider.AccessReadWriteAsync())
            {
                // - Electronics -
                Provider.Get.Electronics.McuTemp.Current = response.mcutemp.cur;
                Provider.Get.Electronics.McuTemp.Min     = response.mcutemp.min;
                Provider.Get.Electronics.McuTemp.Max     = response.mcutemp.max;
                Provider.Get.Electronics.VIn.Current     = response.vin.cur;
                Provider.Get.Electronics.VIn.Min         = response.vin.min;
                Provider.Get.Electronics.VIn.Max         = response.vin.max;

                // - Fans -
                for (int fan = 0; fan < [email protected]; fan++)
                {
                    Fan fanObj;
                    if (fan >= Provider.Get.Fans.Count)
                    {
                        fanObj = new Fan();
                        Provider.Get.Fans.Add(fanObj);
                    }
                    else
                    {
                        fanObj = Provider.Get.Fans[fan];
                    }

                    fanObj.Name  = [email protected][fan];
                    fanObj.Rpm   = (response.sensors.fanRPM.Count > fan && response.sensors.fanRPM[fan] > 0) ? (int?)response.sensors.fanRPM[fan] : null;
                    fanObj.Value = [email protected][fan] / 100F;
                    fanObj.Thermostatic.Control = (response.controllableFans & (1 << fan)) == 0;
                }
                for (int fan = Provider.Get.Fans.Count; fan > [email protected]; fan--)
                {
                    Provider.Get.Fans.RemoveAt(fan - 1);
                }

                // - Move -
                Provider.Get.Move.Compensation = response.compensation;
                Provider.Get.Move.CurrentMove.RequestedSpeed = response.speeds.requested;
                Provider.Get.Move.CurrentMove.TopSpeed       = response.speeds.top;
                Provider.Get.Move.SpeedFactor      = [email protected] / 100;
                Provider.Get.Move.BabystepZ        = [email protected];
                Provider.Get.Move.CurrentWorkplace = response.coords.wpl;

                // Update drives and endstops
                for (int drive = 0; drive < Provider.Get.Move.Drives.Count; drive++)
                {
                    Drive driveObj = Provider.Get.Move.Drives[drive];

                    if (drive < response.axes)
                    {
                        driveObj.Position = response.coords.xyz[drive];
                    }
                    else if (drive < response.axes + response.coords.extr.Count)
                    {
                        driveObj.Position = response.coords.extr[drive - response.axes];
                    }
                    else
                    {
                        driveObj.Position = null;
                    }

                    Endstop endstopObj;
                    if (drive >= Provider.Get.Sensors.Endstops.Count)
                    {
                        endstopObj = new Endstop();
                        Provider.Get.Sensors.Endstops.Add(endstopObj);
                    }
                    else
                    {
                        endstopObj = Provider.Get.Sensors.Endstops[drive];
                    }
                    endstopObj.Triggered = (response.endstops & (1 << drive)) != 0;
                }

                // Update axes
                int axis;
                for (axis = 0; axis < response.totalAxes; axis++)
                {
                    Axis axisObj;
                    if (axis >= Provider.Get.Move.Axes.Count)
                    {
                        axisObj = new Axis();
                        Provider.Get.Move.Axes.Add(axisObj);

                        axisObj.Drives.Add(axis);
                    }
                    else
                    {
                        axisObj = Provider.Get.Move.Axes[axis];

                        if (axisObj.Drives[0] != axis)
                        {
                            axisObj.Drives[0] = axis;
                        }
                    }

                    axisObj.Letter = response.axisNames[axis];
                    if (axis < response.coords.xyz.Count)
                    {
                        axisObj.Homed           = response.coords.axesHomed[axis] != 0;
                        axisObj.MachinePosition = response.coords.machine[axis];
                        axisObj.Visible         = true;
                    }
                    else
                    {
                        axisObj.Homed           = true;
                        axisObj.MachinePosition = null;
                        axisObj.Visible         = false;
                    }
                    axisObj.MinEndstop = axis;
                    axisObj.MaxEndstop = axis;
                }
                for (axis = Provider.Get.Move.Axes.Count; axis > response.totalAxes; axis--)
                {
                    Provider.Get.Move.Axes.RemoveAt(axis - 1);
                }

                // Update extruder drives
                int extruder;
                for (extruder = 0; extruder < response.coords.extr.Count; extruder++)
                {
                    Extruder extruderObj;
                    if (extruder >= Provider.Get.Move.Extruders.Count)
                    {
                        extruderObj = new Extruder();
                        Provider.Get.Move.Extruders.Add(extruderObj);
                    }
                    else
                    {
                        extruderObj = Provider.Get.Move.Extruders[extruder];
                    }

                    extruderObj.Factor = [email protected][extruder] / 100F;
                    if (extruderObj.Drives.Count == 1)
                    {
                        int drive = response.coords.xyz.Count + extruder;
                        if (extruderObj.Drives[0] != drive)
                        {
                            extruderObj.Drives[0] = drive;
                        }
                    }
                    else
                    {
                        extruderObj.Drives.Add(response.coords.xyz.Count + extruder);
                    }
                }
                for (extruder = Provider.Get.Move.Extruders.Count; extruder > response.coords.extr.Count; extruder--)
                {
                    Provider.Get.Move.Extruders.RemoveAt(extruder - 1);
                }

                // - Heat -
                Provider.Get.Heat.ColdExtrudeTemperature = response.coldExtrudeTemp;
                Provider.Get.Heat.ColdRetractTemperature = response.coldRetractTemp;

                // Update heaters
                int heater;
                for (heater = 0; heater < response.temps.current.Count; heater++)
                {
                    Heater heaterObj;
                    if (heater >= Provider.Get.Heat.Heaters.Count)
                    {
                        heaterObj = new Heater();
                        Provider.Get.Heat.Heaters.Add(heaterObj);
                    }
                    else
                    {
                        heaterObj = Provider.Get.Heat.Heaters[heater];
                    }

                    heaterObj.Current = response.temps.current[heater];
                    heaterObj.Max     = response.tempLimit;
                    heaterObj.Name    = response.temps.names[heater];
                    heaterObj.Sensor  = heater;
                    heaterObj.State   = (HeaterState)response.temps.state[heater];
                }
                for (heater = Provider.Get.Heat.Heaters.Count; heater > response.temps.current.Count; heater--)
                {
                    Provider.Get.Heat.Heaters.RemoveAt(heater - 1);
                }

                // Update beds
                if (response.temps.bed != null)
                {
                    BedOrChamber bedObj;
                    if (Provider.Get.Heat.Beds.Count == 0)
                    {
                        bedObj = new BedOrChamber();
                        Provider.Get.Heat.Beds.Add(bedObj);
                    }
                    else
                    {
                        bedObj = Provider.Get.Heat.Beds[0];
                    }

                    if (bedObj.Heaters.Count == 0)
                    {
                        bedObj.Active.Add(response.temps.bed.active);
                        bedObj.Standby.Add(response.temps.bed.standby);
                        bedObj.Heaters.Add(response.temps.bed.heater);
                    }
                    else
                    {
                        if (bedObj.Active[0] != response.temps.bed.active)
                        {
                            bedObj.Active[0] = response.temps.bed.active;
                        }
                        if (bedObj.Standby[0] != response.temps.bed.standby)
                        {
                            bedObj.Standby[0] = response.temps.bed.standby;
                        }
                        if (bedObj.Heaters[0] != response.temps.bed.heater)
                        {
                            bedObj.Heaters[0] = response.temps.bed.heater;
                        }
                    }
                }
                else if (Provider.Get.Heat.Beds.Count > 0)
                {
                    Provider.Get.Heat.Beds.Clear();
                }

                // Update chambers
                if (response.temps.chamber != null)
                {
                    BedOrChamber chamberObj;
                    if (Provider.Get.Heat.Chambers.Count == 0)
                    {
                        chamberObj = new BedOrChamber();
                        Provider.Get.Heat.Chambers.Add(chamberObj);
                    }
                    else
                    {
                        chamberObj = Provider.Get.Heat.Chambers[0];
                    }

                    if (chamberObj.Heaters.Count == 0)
                    {
                        chamberObj.Active.Add(response.temps.chamber.active);
                        chamberObj.Standby.Add(response.temps.chamber.standby);
                        chamberObj.Heaters.Add(response.temps.chamber.heater);
                    }
                    else
                    {
                        if (chamberObj.Active[0] != response.temps.chamber.active)
                        {
                            chamberObj.Active[0] = response.temps.chamber.active;
                        }
                        if (chamberObj.Standby[0] != response.temps.chamber.standby)
                        {
                            chamberObj.Standby[0] = response.temps.chamber.standby;
                        }
                        if (chamberObj.Heaters[0] != response.temps.chamber.heater)
                        {
                            chamberObj.Heaters[0] = response.temps.chamber.heater;
                        }
                    }
                }
                else if (Provider.Get.Heat.Chambers.Count > 0)
                {
                    Provider.Get.Heat.Chambers.Clear();
                }

                // Update extra heaters
                int extra;
                for (extra = 0; extra < response.temps.extra.Count; extra++)
                {
                    ExtraHeater extraObj;
                    if (extra >= Provider.Get.Heat.Extra.Count)
                    {
                        extraObj = new ExtraHeater();
                        Provider.Get.Heat.Extra.Add(extraObj);
                    }
                    else
                    {
                        extraObj = Provider.Get.Heat.Extra[extra];
                    }

                    extraObj.Current = response.temps.extra[extra].temp;
                    extraObj.Name    = response.temps.extra[extra].name;
                }
                for (extra = Provider.Get.Heat.Extra.Count; extra > response.temps.extra.Count; extra--)
                {
                    Provider.Get.Heat.Extra.RemoveAt(extra - 1);
                }

                // - Sensors -
                if (response.probe.type != 0)
                {
                    Probe probeObj;
                    if (Provider.Get.Sensors.Probes.Count == 0)
                    {
                        probeObj = new Probe();
                        Provider.Get.Sensors.Probes.Add(probeObj);
                    }
                    else
                    {
                        probeObj = Provider.Get.Sensors.Probes[0];
                    }

                    probeObj.Type          = (ProbeType)response.probe.type;
                    probeObj.Value         = response.sensors.probeValue;
                    probeObj.Threshold     = response.probe.threshold;
                    probeObj.TriggerHeight = response.probe.height;
                    if (response.sensors.probeSecondary != null)
                    {
                        ListHelpers.SetList(probeObj.SecondaryValues, response.sensors.probeSecondary);
                    }
                    else if (probeObj.SecondaryValues.Count > 0)
                    {
                        probeObj.SecondaryValues.Clear();
                    }
                }
                else if (Provider.Get.Sensors.Probes.Count != 0)
                {
                    Provider.Get.Sensors.Probes.Clear();
                }

                // - Output -
                int            beepDuration = 0, beepFrequency = 0;
                MessageBoxMode?messageBoxMode = null;
                string         displayMessage = string.Empty;
                if (response.output != null)
                {
                    if (response.output.beepFrequency != 0 && response.output.beepDuration != 0)
                    {
                        beepDuration  = response.output.beepDuration;
                        beepFrequency = response.output.beepFrequency;
                    }
                    displayMessage = response.output.message;
                    if (response.output.msgBox != null)
                    {
                        messageBoxMode = (MessageBoxMode)response.output.msgBox.mode;
                        Provider.Get.MessageBox.Title   = response.output.msgBox.title;
                        Provider.Get.MessageBox.Message = response.output.msgBox.msg;
                        for (int i = 0; i < 9; i++)
                        {
                            if ((response.output.msgBox.controls & (1 << i)) != 0)
                            {
                                if (!Provider.Get.MessageBox.AxisControls.Contains(i))
                                {
                                    Provider.Get.MessageBox.AxisControls.Add(i);
                                }
                            }
                            else if (Provider.Get.MessageBox.AxisControls.Contains(i))
                            {
                                Provider.Get.MessageBox.AxisControls.Remove(i);
                            }
                        }
                        Provider.Get.MessageBox.Seq = response.output.msgBox.seq;
                    }
                }
                Provider.Get.State.Beep.Duration  = beepDuration;
                Provider.Get.State.Beep.Frequency = beepFrequency;
                Provider.Get.State.DisplayMessage = displayMessage;
                Provider.Get.MessageBox.Mode      = messageBoxMode;

                // - State -
                MachineStatus oldStatus = Provider.Get.State.Status, newStatus = GetStatus(response.status);
                if ((newStatus == MachineStatus.Idle || newStatus == MachineStatus.Off) &&
                    (oldStatus == MachineStatus.Paused || oldStatus == MachineStatus.Pausing || oldStatus == MachineStatus.Processing ||
                     oldStatus == MachineStatus.Resuming || oldStatus == MachineStatus.Simulating))
                {
                    // No longer processing a file...
                    Provider.Get.Job.File.Assign(new DuetAPI.ParsedFileInfo());
                    Provider.Get.Job.FilePosition = null;
                    Provider.Get.Job.TimesLeft.Assign(new DuetAPI.Machine.TimesLeft());
                }
                Provider.Get.State.AtxPower    = ([email protected] == -1) ? null : (bool?)([email protected] != 0);
                Provider.Get.State.CurrentTool = response.currentTool;
                Provider.Get.State.Status      = newStatus;
                Provider.Get.State.Mode        = (MachineMode)Enum.Parse(typeof(MachineMode), response.mode, true);
                Provider.Get.Network.Name      = response.name;

                // - Tools -
                int tool;
                for (tool = 0; tool < response.tools.Count; tool++)
                {
                    Tool toolObj;
                    if (tool >= Provider.Get.Tools.Count)
                    {
                        toolObj = new Tool();
                        Provider.Get.Tools.Add(toolObj);
                        addedTools.Add(toolObj);
                    }
                    else
                    {
                        toolObj = Provider.Get.Tools[tool];
                    }

                    // FIXME: The filament drive is not part of the status response / OM yet
                    toolObj.FilamentExtruder = (response.tools[tool].drives.Count > 0) ? response.tools[tool].drives[0] : -1;
                    toolObj.Filament         = string.IsNullOrEmpty(response.tools[tool].filament) ? null : response.tools[tool].filament;
                    toolObj.Name             = string.IsNullOrEmpty(response.tools[tool].name) ? null : response.tools[tool].name;
                    toolObj.Number           = response.tools[tool].number;
                    ListHelpers.SetList(toolObj.Heaters, response.tools[tool].heaters);
                    ListHelpers.SetList(toolObj.Extruders, response.tools[tool].drives);
                    ListHelpers.SetList(toolObj.Active, response.temps.tools.active[tool]);
                    ListHelpers.SetList(toolObj.Standby, response.temps.tools.standby[tool]);

                    List <int> fanIndices = new List <int>();
                    for (int i = 0; i < [email protected]; i++)
                    {
                        if ((response.tools[tool].fans & (1 << i)) != 0)
                        {
                            fanIndices.Add(i);
                        }
                    }
                    ListHelpers.SetList(toolObj.Fans, fanIndices);
                    ListHelpers.SetList(toolObj.Offsets, response.tools[tool].offsets);
                }
                for (tool = Provider.Get.Tools.Count; tool > response.tools.Count; tool--)
                {
                    Utility.FilamentManager.ToolRemoved(Provider.Get.Tools[tool - 1]);
                    Provider.Get.Tools.RemoveAt(tool - 1);
                }
            }

            // Notify FilamentManager about added tools. Deal with them here to avoid deadlocks
            foreach (Tool toolObj in addedTools)
            {
                await Utility.FilamentManager.ToolAdded(toolObj);
            }
        }
Exemple #4
0
        /// <summary>
        /// Process status updates in the background
        /// </summary>
        /// <returns></returns>
        public static async Task ProcessUpdates()
        {
            while (await _statusUpdates.OutputAvailableAsync(Program.CancelSource.Token))
            {
                Tuple <byte, byte[]> statusUpdate = await _statusUpdates.TakeAsync(Program.CancelSource.Token);

                try
                {
                    if (statusUpdate.Item1 == 2)
                    {
                        AdvancedStatusResponse response = (AdvancedStatusResponse)JsonSerializer.Deserialize(statusUpdate.Item2, typeof(AdvancedStatusResponse), JsonHelper.DefaultJsonOptions);

                        List <Tool> addedTools = new List <Tool>();
                        using (await Provider.AccessReadWriteAsync())
                        {
                            // - Electronics -
                            Provider.Get.Electronics.McuTemp.Current = response.mcutemp.cur;
                            Provider.Get.Electronics.McuTemp.Min     = response.mcutemp.min;
                            Provider.Get.Electronics.McuTemp.Max     = response.mcutemp.max;
                            Provider.Get.Electronics.VIn.Current     = response.vin.cur;
                            Provider.Get.Electronics.VIn.Min         = response.vin.min;
                            Provider.Get.Electronics.VIn.Max         = response.vin.max;

                            // - Fans -
                            for (int fan = 0; fan < [email protected]; fan++)
                            {
                                Fan fanObj;
                                if (fan >= Provider.Get.Fans.Count)
                                {
                                    fanObj = new Fan();
                                    Provider.Get.Fans.Add(fanObj);
                                }
                                else
                                {
                                    fanObj = Provider.Get.Fans[fan];
                                }

                                fanObj.Name  = [email protected][fan];
                                fanObj.Rpm   = (response.sensors.fanRPM.Count > fan && response.sensors.fanRPM[fan] > 0) ? (int?)response.sensors.fanRPM[fan] : null;
                                fanObj.Value = [email protected][fan] / 100F;
                                fanObj.Thermostatic.Control = (response.controllableFans & (1 << fan)) == 0;
                            }
                            for (int fan = Provider.Get.Fans.Count; fan > [email protected]; fan--)
                            {
                                Provider.Get.Fans.RemoveAt(fan - 1);
                            }

                            // - Move -
                            Provider.Get.Move.Compensation = response.compensation;
                            Provider.Get.Move.CurrentMove.RequestedSpeed = response.speeds.requested;
                            Provider.Get.Move.CurrentMove.TopSpeed       = response.speeds.top;
                            Provider.Get.Move.SpeedFactor      = [email protected] / 100;
                            Provider.Get.Move.BabystepZ        = [email protected];
                            Provider.Get.Move.CurrentWorkplace = response.coords.wpl;

                            // Update drives and endstops
                            for (int drive = 0; drive < Provider.Get.Move.Drives.Count; drive++)
                            {
                                Drive driveObj = Provider.Get.Move.Drives[drive];

                                if (drive < response.axes)
                                {
                                    driveObj.Position = response.coords.xyz[drive];
                                }
                                else if (drive < response.axes + response.coords.extr.Count)
                                {
                                    driveObj.Position = response.coords.extr[drive - response.axes];
                                }
                                else
                                {
                                    driveObj.Position = null;
                                }

                                Endstop endstopObj;
                                if (drive >= Provider.Get.Sensors.Endstops.Count)
                                {
                                    endstopObj = new Endstop();
                                    Provider.Get.Sensors.Endstops.Add(endstopObj);
                                }
                                else
                                {
                                    endstopObj = Provider.Get.Sensors.Endstops[drive];
                                }
                                endstopObj.Triggered = (response.endstops & (1 << drive)) != 0;
                            }

                            // Update axes
                            int axis;
                            for (axis = 0; axis < response.totalAxes; axis++)
                            {
                                Axis axisObj;
                                if (axis >= Provider.Get.Move.Axes.Count)
                                {
                                    axisObj = new Axis();
                                    Provider.Get.Move.Axes.Add(axisObj);
                                }
                                else
                                {
                                    axisObj = Provider.Get.Move.Axes[axis];
                                }

                                axisObj.Letter = response.axisNames[axis];
                                if (axis < response.coords.xyz.Count)
                                {
                                    axisObj.Homed           = response.coords.axesHomed[axis] != 0;
                                    axisObj.MachinePosition = response.coords.machine[axis];
                                    axisObj.Visible         = true;
                                }
                                else
                                {
                                    axisObj.Homed           = true;
                                    axisObj.MachinePosition = null;
                                    axisObj.Visible         = false;
                                }
                                axisObj.MinEndstop = axis;
                                axisObj.MaxEndstop = axis;
                            }
                            for (axis = Provider.Get.Move.Axes.Count; axis > response.totalAxes; axis--)
                            {
                                Provider.Get.Move.Axes.RemoveAt(axis - 1);
                            }

                            // Update extruder drives
                            int extruder;
                            for (extruder = 0; extruder < response.coords.extr.Count; extruder++)
                            {
                                Extruder extruderObj;
                                if (extruder >= Provider.Get.Move.Extruders.Count)
                                {
                                    extruderObj = new Extruder();
                                    Provider.Get.Move.Extruders.Add(extruderObj);
                                }
                                else
                                {
                                    extruderObj = Provider.Get.Move.Extruders[extruder];
                                }

                                extruderObj.Factor = [email protected][extruder] / 100F;
                                if (extruderObj.Drives.Count == 1)
                                {
                                    int drive = response.coords.xyz.Count + extruder;
                                    if (extruderObj.Drives[0] != drive)
                                    {
                                        extruderObj.Drives[0] = drive;
                                    }
                                }
                                else
                                {
                                    extruderObj.Drives.Add(response.coords.xyz.Count + extruder);
                                }
                            }
                            for (extruder = Provider.Get.Move.Extruders.Count; extruder > response.coords.extr.Count; extruder--)
                            {
                                Provider.Get.Move.Extruders.RemoveAt(extruder - 1);
                            }

                            // - Heat -
                            Provider.Get.Heat.ColdExtrudeTemperature = response.coldExtrudeTemp;
                            Provider.Get.Heat.ColdRetractTemperature = response.coldRetractTemp;

                            // Update heaters
                            int heater;
                            for (heater = 0; heater < response.temps.current.Count; heater++)
                            {
                                Heater heaterObj;
                                if (heater >= Provider.Get.Heat.Heaters.Count)
                                {
                                    heaterObj = new Heater();
                                    Provider.Get.Heat.Heaters.Add(heaterObj);
                                }
                                else
                                {
                                    heaterObj = Provider.Get.Heat.Heaters[heater];
                                }

                                heaterObj.Current = response.temps.current[heater];
                                heaterObj.Max     = response.tempLimit;
                                heaterObj.Name    = response.temps.names[heater];
                                heaterObj.Sensor  = heater;
                                heaterObj.State   = (HeaterState)response.temps.state[heater];
                            }
                            for (heater = Provider.Get.Heat.Heaters.Count; heater > response.temps.current.Count; heater--)
                            {
                                Provider.Get.Heat.Heaters.RemoveAt(heater - 1);
                            }

                            // Update beds
                            if (response.temps.bed != null)
                            {
                                BedOrChamber bedObj;
                                if (Provider.Get.Heat.Beds.Count == 0)
                                {
                                    bedObj = new BedOrChamber();
                                    Provider.Get.Heat.Beds.Add(bedObj);
                                }
                                else
                                {
                                    bedObj = Provider.Get.Heat.Beds[0];
                                }

                                if (bedObj.Heaters.Count == 0)
                                {
                                    bedObj.Active.Add(response.temps.bed.active);
                                    bedObj.Standby.Add(response.temps.bed.standby);
                                    bedObj.Heaters.Add(response.temps.bed.heater);
                                }
                                else
                                {
                                    if (bedObj.Active[0] != response.temps.bed.active)
                                    {
                                        bedObj.Active[0] = response.temps.bed.active;
                                    }
                                    if (bedObj.Standby[0] != response.temps.bed.standby)
                                    {
                                        bedObj.Standby[0] = response.temps.bed.standby;
                                    }
                                    if (bedObj.Heaters[0] != response.temps.bed.heater)
                                    {
                                        bedObj.Heaters[0] = response.temps.bed.heater;
                                    }
                                }
                            }
                            else if (Provider.Get.Heat.Beds.Count > 0)
                            {
                                Provider.Get.Heat.Beds.Clear();
                            }

                            // Update chambers
                            if (response.temps.chamber != null)
                            {
                                BedOrChamber chamberObj;
                                if (Provider.Get.Heat.Chambers.Count == 0)
                                {
                                    chamberObj = new BedOrChamber();
                                    Provider.Get.Heat.Chambers.Add(chamberObj);
                                }
                                else
                                {
                                    chamberObj = Provider.Get.Heat.Chambers[0];
                                }

                                if (chamberObj.Heaters.Count == 0)
                                {
                                    chamberObj.Active.Add(response.temps.chamber.active);
                                    chamberObj.Standby.Add(response.temps.chamber.standby);
                                    chamberObj.Heaters.Add(response.temps.chamber.heater);
                                }
                                else
                                {
                                    if (chamberObj.Active[0] != response.temps.chamber.active)
                                    {
                                        chamberObj.Active[0] = response.temps.chamber.active;
                                    }
                                    if (chamberObj.Standby[0] != response.temps.chamber.standby)
                                    {
                                        chamberObj.Standby[0] = response.temps.chamber.standby;
                                    }
                                    if (chamberObj.Heaters[0] != response.temps.chamber.heater)
                                    {
                                        chamberObj.Heaters[0] = response.temps.chamber.heater;
                                    }
                                }
                            }
                            else if (Provider.Get.Heat.Chambers.Count > 0)
                            {
                                Provider.Get.Heat.Chambers.Clear();
                            }

                            // Update extra heaters
                            int extra;
                            for (extra = 0; extra < response.temps.extra.Count; extra++)
                            {
                                ExtraHeater extraObj;
                                if (extra >= Provider.Get.Heat.Extra.Count)
                                {
                                    extraObj = new ExtraHeater();
                                    Provider.Get.Heat.Extra.Add(extraObj);
                                }
                                else
                                {
                                    extraObj = Provider.Get.Heat.Extra[extra];
                                }

                                extraObj.Current = response.temps.extra[extra].temp;
                                extraObj.Name    = response.temps.extra[extra].name;
                            }
                            for (extra = Provider.Get.Heat.Extra.Count; extra > response.temps.extra.Count; extra--)
                            {
                                Provider.Get.Heat.Extra.RemoveAt(extra - 1);
                            }

                            // - Sensors -
                            if (response.probe.type != 0)
                            {
                                Probe probeObj;
                                if (Provider.Get.Sensors.Probes.Count == 0)
                                {
                                    probeObj = new Probe();
                                    Provider.Get.Sensors.Probes.Add(probeObj);
                                }
                                else
                                {
                                    probeObj = Provider.Get.Sensors.Probes[0];
                                }

                                probeObj.Type          = (ProbeType)response.probe.type;
                                probeObj.Value         = response.sensors.probeValue;
                                probeObj.Threshold     = response.probe.threshold;
                                probeObj.TriggerHeight = response.probe.height;
                                if (response.sensors.probeSecondary != null)
                                {
                                    ListHelpers.SetList(probeObj.SecondaryValues, response.sensors.probeSecondary);
                                }
                            }
                            else if (Provider.Get.Sensors.Probes.Count != 0)
                            {
                                Provider.Get.Sensors.Probes.Clear();
                            }

                            // - Output -
                            int            beepDuration = 0, beepFrequency = 0;
                            MessageBoxMode?messageBoxMode = null;
                            string         displayMessage = string.Empty;
                            if (response.output != null)
                            {
                                if (response.output.beepFrequency != 0 && response.output.beepDuration != 0)
                                {
                                    beepDuration  = response.output.beepFrequency;
                                    beepFrequency = response.output.beepDuration;
                                }
                                displayMessage = response.output.message;
                                if (response.output.msgBox != null)
                                {
                                    messageBoxMode = (MessageBoxMode)response.output.msgBox.mode;
                                    Provider.Get.MessageBox.Title   = response.output.msgBox.title;
                                    Provider.Get.MessageBox.Message = response.output.msgBox.msg;
                                    for (int i = 0; i < 9; i++)
                                    {
                                        if ((response.output.msgBox.controls & (1 << i)) != 0)
                                        {
                                            if (!Provider.Get.MessageBox.AxisControls.Contains(i))
                                            {
                                                Provider.Get.MessageBox.AxisControls.Add(i);
                                            }
                                        }
                                        else if (Provider.Get.MessageBox.AxisControls.Contains(i))
                                        {
                                            Provider.Get.MessageBox.AxisControls.Remove(i);
                                        }
                                    }
                                    Provider.Get.MessageBox.Seq = response.output.msgBox.seq;
                                }
                            }
                            Provider.Get.State.Beep.Duration  = beepDuration;
                            Provider.Get.State.Beep.Frequency = beepFrequency;
                            Provider.Get.State.DisplayMessage = displayMessage;
                            Provider.Get.MessageBox.Mode      = messageBoxMode;

                            // - State -
                            Provider.Get.State.AtxPower    = ([email protected] == -1) ? null : (bool?)([email protected] != 0);
                            Provider.Get.State.CurrentTool = response.currentTool;
                            Provider.Get.State.Status      = GetStatus(response.status);
                            Provider.Get.State.Mode        = (MachineMode)Enum.Parse(typeof(MachineMode), response.mode, true);
                            Provider.Get.Network.Name      = response.name;

                            // - Tools -
                            int tool;
                            for (tool = 0; tool < response.tools.Count; tool++)
                            {
                                Tool toolObj;
                                if (tool >= Provider.Get.Tools.Count)
                                {
                                    toolObj = new Tool();
                                    Provider.Get.Tools.Add(toolObj);
                                    addedTools.Add(toolObj);
                                }
                                else
                                {
                                    toolObj = Provider.Get.Tools[tool];
                                }

                                // FIXME: The filament drive is not part of the status response / OM yet
                                toolObj.FilamentExtruder = (response.tools[tool].drives.Count > 0) ? response.tools[tool].drives[0] : -1;
                                toolObj.Filament         = string.IsNullOrEmpty(response.tools[tool].filament) ? null : response.tools[tool].filament;
                                toolObj.Name             = string.IsNullOrEmpty(response.tools[tool].name) ? null : response.tools[tool].name;
                                toolObj.Number           = response.tools[tool].number;
                                ListHelpers.SetList(toolObj.Heaters, response.tools[tool].heaters);
                                ListHelpers.SetList(toolObj.Extruders, response.tools[tool].drives);
                                ListHelpers.SetList(toolObj.Active, response.temps.tools.active[tool]);
                                ListHelpers.SetList(toolObj.Standby, response.temps.tools.standby[tool]);

                                List <int> fanIndices = new List <int>();
                                for (int i = 0; i < [email protected]; i++)
                                {
                                    if ((response.tools[tool].fans & (1 << i)) != 0)
                                    {
                                        fanIndices.Add(i);
                                    }
                                }
                                ListHelpers.SetList(toolObj.Fans, fanIndices);
                                ListHelpers.SetList(toolObj.Offsets, response.tools[tool].offsets);
                            }
                            for (tool = Provider.Get.Tools.Count; tool > response.tools.Count; tool--)
                            {
                                Utility.FilamentManager.ToolRemoved(Provider.Get.Tools[tool - 1]);
                                Provider.Get.Tools.RemoveAt(tool - 1);
                            }
                        }

                        // Notify FilamentManager about added tools. Deal with them here to avoid deadlocks
                        foreach (Tool toolObj in addedTools)
                        {
                            await Utility.FilamentManager.ToolAdded(toolObj);
                        }
                    }
                    else if (statusUpdate.Item1 == 3)
                    {
                        // Deserialize print status response
                        PrintStatusResponse printResponse = (PrintStatusResponse)JsonSerializer.Deserialize(statusUpdate.Item2, typeof(PrintStatusResponse), JsonHelper.DefaultJsonOptions);

                        using (await Provider.AccessReadWriteAsync())
                        {
                            if (printResponse.currentLayer > Provider.Get.Job.Layers.Count + 1)
                            {
                                // Layer complete
                                Layer lastLayer = (Provider.Get.Job.Layers.Count > 0)
                                    ? Provider.Get.Job.Layers[Provider.Get.Job.Layers.Count - 1]
                                        : new Layer()
                                {
                                    Filament = new List <float>(printResponse.extrRaw.Count)
                                };

                                float   lastHeight = 0F, lastDuration = 0F, lastProgress = 0F;
                                float[] lastFilamentUsage = new float[printResponse.extrRaw.Count];
                                foreach (Layer l in Provider.Get.Job.Layers)
                                {
                                    lastHeight   += l.Height;
                                    lastDuration += l.Duration;
                                    lastProgress += l.FractionPrinted;
                                    for (int i = 0; i < Math.Min(lastFilamentUsage.Length, l.Filament.Count); i++)
                                    {
                                        lastFilamentUsage[i] += l.Filament[i];
                                    }
                                }

                                float[] filamentUsage = new float[printResponse.extrRaw.Count];
                                for (int i = 0; i < filamentUsage.Length; i++)
                                {
                                    filamentUsage[i] = printResponse.extrRaw[i] - lastFilamentUsage[i];
                                }

                                float printDuration = printResponse.printDuration - printResponse.warmUpDuration;
                                Layer layer         = new Layer
                                {
                                    Duration        = printDuration - lastDuration,
                                    Filament        = new List <float>(filamentUsage),
                                    FractionPrinted = (printResponse.fractionPrinted / 100F) - lastProgress,
                                    Height          = (printResponse.currentLayer > 2) ? _currentHeight - lastHeight : printResponse.firstLayerHeight
                                };
                                Provider.Get.Job.Layers.Add(layer);

                                // FIXME: In case Z isn't mapped to the 3rd axis...
                                _currentHeight = printResponse.coords.xyz[2];
                            }
                            else if (printResponse.currentLayer < Provider.Get.Job.Layers.Count && GetStatus(printResponse.status) == MachineStatus.Processing)
                            {
                                // Starting a new print job
                                Provider.Get.Job.Layers.Clear();
                                _currentHeight = 0F;
                            }

                            Provider.Get.Job.Layer        = printResponse.currentLayer;
                            Provider.Get.Job.LayerTime    = (printResponse.currentLayer == 1) ? printResponse.firstLayerDuration : printResponse.currentLayerTime;
                            Provider.Get.Job.FilePosition = printResponse.filePosition;
                            ListHelpers.SetList(Provider.Get.Job.ExtrudedRaw, printResponse.extrRaw);
                            Provider.Get.Job.Duration           = printResponse.printDuration;
                            Provider.Get.Job.WarmUpDuration     = printResponse.warmUpDuration;
                            Provider.Get.Job.TimesLeft.File     = (printResponse.timesLeft.file > 0F) ? (float?)printResponse.timesLeft.file : null;
                            Provider.Get.Job.TimesLeft.Filament = (printResponse.timesLeft.filament > 0F) ? (float?)printResponse.timesLeft.filament : null;
                            Provider.Get.Job.TimesLeft.Layer    = (printResponse.timesLeft.layer > 0F) ? (float?)printResponse.timesLeft.layer : null;
                        }

                        // Notify waiting threads about the model update
                        _updateEvent.Set();
                        _updateEvent.Reset();
                    }
                    else if (statusUpdate.Item1 == 5)
                    {
                        // Deserialize config response
                        ConfigResponse configResponse = (ConfigResponse)JsonSerializer.Deserialize(statusUpdate.Item2, typeof(ConfigResponse), JsonHelper.DefaultJsonOptions);

                        if (configResponse.axisMins == null)
                        {
                            Console.WriteLine("[warn] Config response unsupported. Update your firmware!");
                            return;
                        }

                        using (await Provider.AccessReadWriteAsync())
                        {
                            // - Axes -
                            for (int axis = 0; axis < Math.Min(Provider.Get.Move.Axes.Count, configResponse.axisMins.Count); axis++)
                            {
                                Provider.Get.Move.Axes[axis].Min = configResponse.axisMins[axis];
                                Provider.Get.Move.Axes[axis].Max = configResponse.axisMaxes[axis];
                            }

                            // - Drives -
                            int drive;
                            for (drive = 0; drive < configResponse.accelerations.Count; drive++)
                            {
                                Drive driveObj;
                                if (drive >= Provider.Get.Move.Drives.Count)
                                {
                                    driveObj = new Drive();
                                    Provider.Get.Move.Drives.Add(driveObj);
                                }
                                else
                                {
                                    driveObj = Provider.Get.Move.Drives[drive];
                                }

                                driveObj.Acceleration = configResponse.accelerations[drive];
                                driveObj.Current      = configResponse.currents[drive];
                                driveObj.MinSpeed     = configResponse.minFeedrates[drive];
                                driveObj.MaxSpeed     = configResponse.maxFeedrates[drive];
                            }
                            for (drive = Provider.Get.Move.Drives.Count; drive > configResponse.accelerations.Count; drive--)
                            {
                                Provider.Get.Move.Drives.RemoveAt(drive - 1);
                                Provider.Get.Sensors.Endstops.RemoveAt(drive - 1);
                            }

                            // - Electronics -
                            Provider.Get.Electronics.Name      = configResponse.firmwareElectronics;
                            Provider.Get.Electronics.ShortName = configResponse.boardName;
                            switch (Provider.Get.Electronics.ShortName)
                            {
                            case "MBP05":
                                Provider.Get.Electronics.Revision = "0.5";
                                break;

                            case "MB6HC":
                                Provider.Get.Electronics.Revision = "0.6";
                                break;
                            }
                            Provider.Get.Electronics.Firmware.Name    = configResponse.firmwareName;
                            Provider.Get.Electronics.Firmware.Version = configResponse.firmwareVersion;
                            Provider.Get.Electronics.Firmware.Date    = configResponse.firmwareDate;

                            // - Move -
                            Provider.Get.Move.Idle.Factor  = configResponse.idleCurrentFactor / 100F;
                            Provider.Get.Move.Idle.Timeout = configResponse.idleTimeout;
                        }

                        // Check if the firmware is supposed to be updated only. When this finishes, DCS is terminated.
                        if (configResponse.boardName != null && Program.UpdateOnly && !_updatingFirmware)
                        {
                            _updatingFirmware = true;

                            Code updateCode = new Code
                            {
                                Type        = DuetAPI.Commands.CodeType.MCode,
                                MajorNumber = 997,
                                Flags       = DuetAPI.Commands.CodeFlags.IsPrioritized
                            };
                            _ = updateCode.Execute();
                        }
                    }
                }
                catch (JsonException e)
                {
                    Console.WriteLine($"[err] Failed to merge JSON: {e}");
                }
            }
        }