diff --git a/Changelog.txt b/Changelog.txt index c62e24c..4d5e87d 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,20 @@ +Version 5.6.4: +- Dampf-/Thermoblock-Temperatur wird jetzt mit einer Nachkommastelle angezeigt - im Webinterface, in + den Verlaufskurven und auf dem Touch-Display. Bisher wurde der Messwert direkt nach dem Einlesen auf + ganze Grad gerundet; die Nachkommastelle war damit schon weg, bevor irgendeine Anzeige sie sehen + konnte. Die einzige Ausnahme war die Offset-Kompensation, die im kalten Bereich den Rohwert + durchreicht - daher stand beim Wasserkessel dort zufällig eine Nachkommastelle, im Betrieb aber nie. +- Das gilt genauso für den Wasserkessel und den Zusatzsensor (Gehäuse/Tassenablage), damit alle + Temperaturen einheitlich aufgelöst sind. +- Die Regelung bleibt unverändert: PID, Sicherheitsabschaltungen und Auto-Tune arbeiten weiter mit dem + auf ganze Grad gerundeten Messwert. Die feinere Zahl entsteht auf einem getrennten Anzeigeweg, damit + sich das Heizverhalten durch diese Änderung nicht verschiebt. +- Die letzte Stelle ist leicht geglättet. Ungefiltert würde sie ständig springen - das Thermoelement + am Dampfkessel liefert ein 0,25-Grad-Raster, der NTC am Wasserkessel das Rauschen des ADC. Ein + größerer Temperatursprung wird weiterhin sofort übernommen, die Anzeige läuft also nicht nach. +- Das kleine OLED zeigt weiterhin ganze Grad (dort ist kein Platz), rundet den Wert jetzt aber, statt + die Nachkommastelle abzuschneiden. + Version 5.6.3: - Bug-Fix: Beim Aufwecken über das Touch-Display blieb die Auswahl „Espresso oder Cold Extraction" manchmal aus, während sie über den Schalter kam. Ursache waren zwei unterschiedliche Bedingungen für diff --git a/Dual-PID.ino b/Dual-PID.ino index 41fa930..52f7a3f 100644 --- a/Dual-PID.ino +++ b/Dual-PID.ino @@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire); * Firmware-Informationen ************************************************************************************/ -String version = "5.6.3"; +String version = "5.6.4"; String versionHersteller = "Thomas Müller"; String versionHerstellerMail = "thomas@mueller.black"; @@ -603,6 +603,10 @@ double SetpointWasser, InputWasser, OutputWasser; double OffsetDampf = 0.0, OffsetWasser = 0.0; double OffsetCase = 0.0; double RawInputDampf = NAN, RawInputWasser = NAN; +// Geglaettete Rohwerte - ausschliesslich fuer die Anzeige. Die Regelung arbeitet weiter mit +// den auf ganze Grad gerundeten Input*-Werten, damit sich am Regelverhalten nichts aendert; +// hier entsteht nur die feinere Zahl fuers Auge. +double SmoothRawDampf = NAN, SmoothRawWasser = NAN, SmoothRawCase = NAN; double displayReferenceRawDampf = NAN, displayReferenceRawWasser = NAN; bool offsetCompensationEnabled = false; const double OFFSET_COMPENSATION_MAX_REFERENCE_RAW_C = 40.0; @@ -621,6 +625,33 @@ void updateOffsetCompensationReferences() { } } +// Tiefpass fuer die Anzeigewerte. Ohne ihn zappelt die Nachkommastelle dauernd: Das +// Thermoelement am Dampfkessel liefert ein 0,25-Grad-Raster, der NTC am Wasserkessel das +// Rauschen des ADC. Ein grosser Sprung (Kaltstart, Sensorwechsel, Bezug) wird sofort +// uebernommen, damit die Anzeige nicht hinterherlaeuft. +const double DISPLAY_TEMP_SMOOTHING = 0.25; // Gewicht des neuen Messwerts je Messtakt +const double DISPLAY_TEMP_JUMP_RESET_C = 5.0; // ab dieser Abweichung ungeglaettet uebernehmen + +void updateSmoothedRawTemperature(double &smoothed, double measured) { + if (!isfinite(measured)) { + smoothed = measured; + return; + } + if (!isfinite(smoothed) || fabs(measured - smoothed) >= DISPLAY_TEMP_JUMP_RESET_C) { + smoothed = measured; + return; + } + smoothed += DISPLAY_TEMP_SMOOTHING * (measured - smoothed); +} + +// Anzeigewerte auf eine Nachkommastelle festzurren, damit Weboberflaeche, Display und +// Verlaufskurven denselben Wert sehen und die letzte Stelle nicht zwischen den Kanaelen +// auseinanderlaeuft. +double quantizeDisplayTemperature(double value) { + if (!isfinite(value)) return value; + return round(value * 10.0) / 10.0; +} + double getDisplayTemperature(double correctedTemp, double rawTemp, double offset, double referenceRaw) { if (!offsetCompensationEnabled || offset >= 0.0 || !isfinite(correctedTemp) || !isfinite(rawTemp) || !isfinite(referenceRaw)) { return correctedTemp; @@ -634,11 +665,17 @@ double getDisplayTemperature(double correctedTemp, double rawTemp, double offset } double getDisplayedWaterTemperature() { - return getDisplayTemperature(InputWasser, RawInputWasser, OffsetWasser, displayReferenceRawWasser); + return quantizeDisplayTemperature(getDisplayTemperature( + SmoothRawWasser + OffsetWasser, SmoothRawWasser, OffsetWasser, displayReferenceRawWasser)); } double getDisplayedSteamTemperature() { - return getDisplayTemperature(InputDampf, RawInputDampf, OffsetDampf, displayReferenceRawDampf); + return quantizeDisplayTemperature(getDisplayTemperature( + SmoothRawDampf + OffsetDampf, SmoothRawDampf, OffsetDampf, displayReferenceRawDampf)); +} + +double getDisplayedCaseTemperature() { + return quantizeDisplayTemperature(SmoothRawCase + OffsetCase); } // PID-Konstanten (Regelungsparameter) @@ -3243,6 +3280,7 @@ void resetToDefaults() { digitalWrite(SSR_LIGHT_PIN, LOW); standbyModeActive = false; InputCase = NAN; + SmoothRawCase = NAN; caseSensorError = false; // PID Limits neu setzen (falls Fenstergröße geändert wurde) @@ -5546,12 +5584,17 @@ void loop() { RawInputWasser = readNTCTemperature(); InputDampf = round(RawInputDampf + OffsetDampf); InputWasser = round(RawInputWasser + OffsetWasser); + updateSmoothedRawTemperature(SmoothRawDampf, RawInputDampf); + updateSmoothedRawTemperature(SmoothRawWasser, RawInputWasser); updateOffsetCompensationReferences(); if (caseSensorEnabled) { - InputCase = round(readCaseTemperature() + OffsetCase); + double rawCase = readCaseTemperature(); + InputCase = round(rawCase + OffsetCase); + updateSmoothedRawTemperature(SmoothRawCase, rawCase); caseSensorError = (isnan(InputCase) || InputCase < 0.0); } else { InputCase = NAN; + SmoothRawCase = NAN; caseSensorError = false; } yield(); @@ -6456,7 +6499,7 @@ if (scaleModeActive) { display.println(F("Heizt auf 93 C")); display.println(F("vor...")); display.print(F("Wasser: ")); - display.print((int)displayedWaterTemp); + display.print((int)lround(displayedWaterTemp)); display.print(F("/")); display.print((int)cleaningAssistantWaterSetpoint); display.print(F(" ")); @@ -6480,7 +6523,7 @@ if (scaleModeActive) { display.print((remainingMs + 999UL) / 1000UL); display.println(F(" s")); display.print(F("Wasser: ")); - display.print((int)displayedWaterTemp); + display.print((int)lround(displayedWaterTemp)); display.print(F("/")); display.print((int)cleaningAssistantWaterSetpoint); display.print(F(" ")); @@ -6522,7 +6565,7 @@ if (scaleModeActive) { else { display.print(F("Init")); } display.println(); display.print("Temperatur:"); - display.print((int)(autoTuneWasserActive ? displayedWaterTemp : displayedSteamTemp)); + display.print((int)lround(autoTuneWasserActive ? displayedWaterTemp : displayedSteamTemp)); display.print(F(" ")); display.print((char)247); display.print(F("C")); @@ -6553,7 +6596,7 @@ if (scaleModeActive) { display.println(F("wenn Temp. erreicht!")); display.println(F(" ")); display.println(F("Temperatur: ")); - display.print((int)displayedWaterTemp); + display.print((int)lround(displayedWaterTemp)); display.print(F(" / ")); display.print((int)SetpointWasser); display.print(F(" ")); @@ -6832,13 +6875,13 @@ if (scaleModeActive) { else if (wasserSensorError) { display.print(F("Sensorfehler!")); } else if (coldExtractionModeActive) { // Cold Extraction: kein Sollwert-Vergleich sinnvoll, der Wasserkreis heizt nicht - display.print((int)displayedWaterTemp); + display.print((int)lround(displayedWaterTemp)); display.print(F(" ")); display.print((char)247); display.print(F("C (COLD)")); } else { - display.print((int)displayedWaterTemp); + display.print((int)lround(displayedWaterTemp)); display.print(F(" / ")); display.print((int)SetpointWasser); display.print(F(" ")); @@ -6857,7 +6900,7 @@ if (scaleModeActive) { bool steamDelayActiveSystem = (dampfVerzoegerung > 0 && (millis() - startupTime < (unsigned long)dampfVerzoegerung * 60000UL)); bool shouldShowDelayMessage = steamDelayActiveSystem && !steamDelayOverridden; if (!shouldShowDelayMessage) { - display.print((int)displayedSteamTemp); + display.print((int)lround(displayedSteamTemp)); display.print(F(" / ")); display.print((int)SetpointDampf); display.print(F(" ")); @@ -8311,9 +8354,9 @@ void recordResetCheckpoint(uint16_t checkpoint) { if (steamHeatDisabledByUser) flags |= RESET_DIAG_FLAG_STEAM_HEAT_DISABLED; rtcResetDiag.flags = flags; - rtcResetDiag.waterTempDeciC = encodeResetDiagSignedTenths((float)InputWasser); - rtcResetDiag.steamTempDeciC = encodeResetDiagSignedTenths((float)InputDampf); - rtcResetDiag.caseTempDeciC = encodeResetDiagSignedTenths((float)InputCase); + rtcResetDiag.waterTempDeciC = encodeResetDiagSignedTenths((float)getDisplayedWaterTemperature()); + rtcResetDiag.steamTempDeciC = encodeResetDiagSignedTenths((float)getDisplayedSteamTemperature()); + rtcResetDiag.caseTempDeciC = encodeResetDiagSignedTenths((float)getDisplayedCaseTemperature()); rtcResetDiag.weightDeciG = encodeResetDiagSignedTenths(currentWeightReading); rtcResetDiag.scaleType = scaleType; rtcResetDiag.wifiStatus = (int8_t)WiFi.status(); @@ -11533,7 +11576,7 @@ void handleChartData(AsyncWebServerRequest *request) { if (!caseSensorEnabled || caseSensorError || isnan(InputCase)) { response->print(F("null")); } else { - snprintf(buffer, sizeof(buffer), "%.1f", InputCase); + snprintf(buffer, sizeof(buffer), "%.1f", getDisplayedCaseTemperature()); response->print(buffer); } @@ -16544,7 +16587,7 @@ void buildDashboardJson(char *buffer, size_t bufferSize) { if (!caseTempDashboardActive || caseSensorError || isnan(InputCase)) { offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":null,"); } else { - dtostrf(InputCase, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":%s,", floatBuf); + dtostrf(getDisplayedCaseTemperature(), 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":%s,", floatBuf); } // Shot-Daten @@ -16973,7 +17016,7 @@ static void buildTouchUartStateJson(char *buffer, size_t bufferSize) { if (!caseSensorEnabled || caseSensorError || isnan(InputCase)) { offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":null,"); } else { - dtostrf(InputCase, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":%s,", floatBuf); + dtostrf(getDisplayedCaseTemperature(), 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTemp\":%s,", floatBuf); } offset += snprintf(buffer + offset, bufferSize - offset, "\"maintenanceInterval\":%d,", maintenanceInterval); diff --git a/P4_Display_Firmware/Changelog.txt b/P4_Display_Firmware/Changelog.txt index f814ef6..a9367d5 100644 --- a/P4_Display_Firmware/Changelog.txt +++ b/P4_Display_Firmware/Changelog.txt @@ -1,3 +1,16 @@ +Version 1.12.0: +- Die Dampf-/Thermoblock-Temperatur wird jetzt mit einer Nachkommastelle angezeigt, wie die + Wassertemperatur. Das gilt für die große Zahl auf der Startseite, den Sollwert darunter und + die Beschriftung der Verlaufskurve. Der Wert kam schon immer fein genug über die Verbindung + zur Maschine an - nur die Anzeige hat die Stelle weggeworfen. Voraussetzung ist die + Maschinen-Firmware 5.6.4, davor liefert die Maschine immer ganze Grad. +- Die große Temperaturzahl verkleinert sich automatisch, wenn sie sonst nicht mehr in die + Karte passt. "124,5°C" ist rund ein Drittel breiter als "124°C", und in der randlosen + Flächen-Darstellung reicht die halbe Bildschirmbreite dafür nicht. Bisher brach LVGL die + Zeile dann um und die zweite Zeile fiel aus der Karte - das betraf schon vorher die + Wassertemperatur, sobald sie 100 °C erreichte. +- Im Reinigungsassistenten steht die Wassertemperatur ebenfalls mit einer Nachkommastelle. + Version 1.11.3: - Der Punkt sitzt wieder genau auf dem letzten Messwert, statt eine halbe Punktbreite davor. Die Linie schaut damit rechts nicht mehr unter ihm hervor. Damit trotzdem nichts diff --git a/P4_Display_Firmware/config.h b/P4_Display_Firmware/config.h index 9cd6a16..d778b12 100644 --- a/P4_Display_Firmware/config.h +++ b/P4_Display_Firmware/config.h @@ -126,7 +126,7 @@ // ------------------------------------------------------------------------------------- // Firmware // ------------------------------------------------------------------------------------- -#define DISPLAY_FW_VERSION "1.11.3" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) +#define DISPLAY_FW_VERSION "1.12.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) // ------------------------------------------------------------------------------------- // Protokoll diff --git a/P4_Display_Firmware/ui.cpp b/P4_Display_Firmware/ui.cpp index c537e5e..e3d8224 100644 --- a/P4_Display_Firmware/ui.cpp +++ b/P4_Display_Firmware/ui.cpp @@ -1098,6 +1098,9 @@ static lv_obj_t* tempField(lv_obj_t* parent, const char* title, lv_color_t accen // auseinandergezogen; ein paar Pixel weniger binden die Ziffern zu einer Zahl zusammen. if (valueFont == &lv_font_maven_pro_bold_140) lv_obj_set_style_text_letter_space(*big, -3, 0); else if (valueFont == &lv_font_maven_pro_bold_96) lv_obj_set_style_text_letter_space(*big, -2, 0); + // Ausgangsschrift merken: big_temp_apply() darf nur von hier aus verkleinern und muss + // zurueckfinden, sobald der Text wieder kuerzer wird. + lv_obj_set_user_data(*big, (void*)valueFont); *setp = lv_label_create(c); lv_label_set_text(*setp, "Soll --.-"); @@ -1120,6 +1123,79 @@ static lv_obj_t* tempField(lv_obj_t* parent, const char* title, lv_color_t accen return c; } +static int32_t big_temp_letter_space(const lv_font_t* f) { + if (f == &lv_font_maven_pro_bold_140) return -3; + if (f == &lv_font_maven_pro_bold_96) return -2; + return 0; +} + +// Schriftleiter fuer die grosse Temperaturzahl, von gross nach klein. +static const lv_font_t* const kBigTempLadder[] = { + &lv_font_maven_pro_bold_140, &lv_font_maven_pro_bold_96, + &lv_font_maven_pro_48, &lv_font_maven_pro_40, + &lv_font_maven_pro_28 +}; +static const int kBigTempLadderLen = (int)(sizeof(kBigTempLadder) / sizeof(kBigTempLadder[0])); + +static const lv_font_t* big_temp_base(lv_obj_t* label) { + const lv_font_t* base = (const lv_font_t*)lv_obj_get_user_data(label); + return base ? base : lv_obj_get_style_text_font(label, LV_PART_MAIN); +} + +// Kleinste Stufe, in die der Text noch ohne Umbruch passt. Breite UND Hoehe der +// Beschriftung sind fest (siehe tempField) - laeuft der Text ueber, bricht LVGL die Zeile +// um und die zweite Zeile faellt aus der Karte heraus, statt nur eng zu wirken. Mit der +// Nachkommastelle ist "124,5°C" rund ein Drittel breiter als "124°C"; in der randlosen +// Flaechen-Darstellung reicht die halbe Bildschirmbreite dafuer nicht mehr. +// -1 heisst "nicht entscheidbar" - dann bleibt es bei der Ausgangsschrift. +static int big_temp_fit_index(lv_obj_t* label, const char* txt) { + const lv_font_t* base = big_temp_base(label); + int idx = 0; + while (idx < kBigTempLadderLen && kBigTempLadder[idx] != base) idx++; + if (idx >= kBigTempLadderLen) return -1; // Schrift nicht in der Leiter + int32_t avail = lv_obj_get_content_width(label); + if (avail <= 0) return idx; // vor dem ersten Layout noch unbekannt + for (; idx < kBigTempLadderLen - 1; idx++) { + lv_point_t sz; + lv_text_get_size(&sz, txt, kBigTempLadder[idx], big_temp_letter_space(kBigTempLadder[idx]), + 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE); + if (sz.x <= avail) break; + } + return idx; +} + +static void big_temp_apply(lv_obj_t* label, int idx, const char* txt) { + const lv_font_t* base = big_temp_base(label); + const lv_font_t* pick = (idx >= 0 && idx < kBigTempLadderLen) ? kBigTempLadder[idx] : base; + if (lv_obj_get_style_text_font(label, LV_PART_MAIN) != pick) { + lv_obj_set_style_text_font(label, pick, 0); + lv_obj_set_style_text_letter_space(label, big_temp_letter_space(pick), 0); + // Die Hoehe bleibt die der Ausgangsschrift (sonst raeumt LVGL das Layout der ganzen + // Seite neu). Die kleinere Zahl wird darin per Innenabstand mittig gehalten, damit + // sie nicht an der Oberkante klebt. + int32_t padTop = (lv_font_get_line_height(base) - lv_font_get_line_height(pick)) / 2; + lv_obj_set_style_pad_top(label, padTop > 0 ? padTop : 0, 0); + } + label_set_if_changed(label, txt); +} + +// Setzt beide grossen Temperaturzahlen gemeinsam. Gleich grosse Felder muessen gleich gross +// bleiben: Muss eine Zahl kleiner gesetzt werden, geht die andere mit. Sonst schrumpfte +// allein der Dampfkessel, weil "125,5°C" breiter ist als "93,5°C" - zwei verschieden +// grosse Zahlen nebeneinander sehen aus wie ein Fehler. Wo die Felder absichtlich ungleich +// sind (gewichtete Darstellung), bleibt jede Zahl bei ihrer eigenen Stufe. +static void big_temp_set_pair(lv_obj_t* a, const char* ta, lv_obj_t* b, const char* tb) { + if (!a || !b || !ta || !tb) return; + int ia = big_temp_fit_index(a, ta); + int ib = big_temp_fit_index(b, tb); + if (ia >= 0 && ib >= 0 && big_temp_base(a) == big_temp_base(b)) { + int common = (ia > ib) ? ia : ib; + ia = ib = common; + } + big_temp_apply(a, ia, ta); + big_temp_apply(b, ib, tb); +} + // Flache Verlaufskurve unter einer Temperatur. Zeigt, ob die Regelung haelt - das ist die // eigentliche Frage an eine PID-Regelung, und eine Zahl kann sie grundsaetzlich nicht // beantworten. Gespeist wird sie aus derselben Sekundenabtastung wie die Verlaufsseite. @@ -2202,7 +2278,7 @@ static void chart_sample_cb(lv_timer_t*) { (int)(dw + 0.5f), (int)(dd + 0.5f)); if (g_legW) lv_label_set_text(g_legW, ("Wasser " + tempStr(g_state.tempW, 1)).c_str()); - if (g_legD) lv_label_set_text(g_legD, ("Dampf " + tempStr(g_state.tempD, 0)).c_str()); + if (g_legD) lv_label_set_text(g_legD, ("Dampf " + tempStr(g_state.tempD, 1)).c_str()); if (g_legC) { if (caseOn) { const char* name = (g_state.caseSensorType == 1) ? "Tassenablage " : "Gehäuse "; @@ -2766,13 +2842,13 @@ static void cln_refresh() { String s; if (st.cleaningWaiting) { s = "Heizt auf 93 °C vor...\n\n"; - s += "Wasser: " + tempStr(st.tempW, 0) + " / 93°C\n\n"; + s += "Wasser: " + tempStr(st.tempW, 1) + " / 93°C\n\n"; s += "Die Zyklen starten automatisch,\nsobald die Temperatur erreicht ist."; } else { s = "Zyklus " + String(st.cleaningCycle) + " / " + String(st.cleaningCycles) + "\n\n"; s += "Phase: " + String(st.cleaningBrewPhase ? "Bezug" : "Pause") + "\n"; s += "Rest: " + String(st.cleaningPhaseRemainSec) + " s\n\n"; - s += "Wasser: " + tempStr(st.tempW, 0) + " / 93°C\n"; + s += "Wasser: " + tempStr(st.tempW, 1) + " / 93°C\n"; s += "Dampfheizung aus"; } if (g_clnProgLbl) label_set_if_changed(g_clnProgLbl, s.c_str()); @@ -4749,14 +4825,20 @@ void ui_update(const MachineState& st) { // Dashboard - Temperaturkarten (im Fehlerfall Warnsymbol + Klartext statt Zahl, wie OLED) if (lblTempW) { - if (st.wasserSafetyShutdown || st.wasserSensorError) { - label_set_if_changed(lblTempW, LV_SYMBOL_WARNING); + // Beide grossen Zahlen zusammen setzen, damit sie bei knapper Kartenbreite + // gemeinsam eine Stufe kleiner werden (siehe big_temp_set_pair). + const bool warnW = st.wasserSafetyShutdown || st.wasserSensorError; + const bool warnD = st.dampfSafetyShutdown || st.dampfSensorError; + String bigW = warnW ? String(LV_SYMBOL_WARNING) : tempStr(st.tempW, 1); + String bigD = warnD ? String(LV_SYMBOL_WARNING) : tempStr(st.tempD, 1); + big_temp_set_pair(lblTempW, bigW.c_str(), lblTempD, bigD.c_str()); + + if (warnW) { label_color_if_changed(lblTempW, COL_DANGER); label_set_if_changed(lblSetW, st.wasserSafetyShutdown ? "Sicherheitsabsch." : "Sensorfehler!"); label_color_if_changed(lblSetW, COL_DANGER); temp_progress_set(barDutyW, arcTempW, bulbTempW, 0, COL_DANGER); } else { - label_set_if_changed(lblTempW, tempStr(st.tempW, 1).c_str()); if (st.cxActive) { // Cold Extraction: der Sollwert ist bedeutungslos, es wird nicht geheizt. // Statt eines Zielbands, das nie erreicht wird, den Grund anzeigen. @@ -4775,17 +4857,15 @@ void ui_update(const MachineState& st) { temp_progress_set(barDutyW, arcTempW, bulbTempW, pctW, colW); } } - if (st.dampfSafetyShutdown || st.dampfSensorError) { - label_set_if_changed(lblTempD, LV_SYMBOL_WARNING); + if (warnD) { label_color_if_changed(lblTempD, COL_DANGER); label_set_if_changed(lblSetD, st.dampfSafetyShutdown ? "Sicherheitsabsch." : "Sensorfehler!"); label_color_if_changed(lblSetD, COL_DANGER); temp_progress_set(barDutyD, arcTempD, bulbTempD, 0, COL_DANGER); } else { - label_set_if_changed(lblTempD, tempStr(st.tempD, 0).c_str()); lv_color_t colD = temp_state_color(st.tempD, st.setD); label_color_if_changed(lblTempD, temp_value_color(colD)); - label_set_if_changed(lblSetD, ("Soll " + tempStr(st.setD, 0)).c_str()); + label_set_if_changed(lblSetD, ("Soll " + tempStr(st.setD, 1)).c_str()); label_color_if_changed(lblSetD, COL_TEXT_DIM); int pctD = (st.setD > 0.0f) ? (int)(st.tempD / st.setD * 100.0f + 0.5f) : 0; temp_progress_set(barDutyD, arcTempD, bulbTempD, pctD, colD);