perf(P4): Nur bei echter Änderung neu zeichnen (1.9.2)

Die erweiterte Messung war eindeutig: "1 Bereich, 100 % der Fläche" - LVGL
zeichnete bei jeder Zustandsmeldung den kompletten Bildschirm neu, also
einmal pro Sekunde 240-300 ms lang. Kein Rendering-Tempoproblem, sondern
massiv zu viel Invalidierung.

lv_label_set_text invalidiert den Label-Bereich unbedingt, auch bei
identischem Text; lv_obj_set_style_text_color ebenso. Auf dem Dashboard
wurden pro Sekunde Werte quer über den Bildschirm gesetzt (Uhr oben,
Temperaturen Mitte, Bezugswerte unten) - LVGL joint die Bereiche, und die
Bounding-Box ist die ganze Fläche.

- Neue Helfer label_fmt_if_changed() und label_color_if_changed() neben dem
  vorhandenen label_set_if_changed().
- Alle 13 zyklischen Textzuweisungen und alle 10 Farbzuweisungen in
  ui_update darauf umgestellt.

Da sich Temperaturen selten um ein Zehntelgrad ändern und Sollwerte
praktisch nie, entfallen die meisten Neuzeichnungen ganz.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
raw-designs
2026-09-03 02:17:03 +02:00
co-authored by Claude Opus 5
parent 484812cb4c
commit e32c654b8d
2 changed files with 65 additions and 23 deletions
+16
View File
@@ -1,3 +1,19 @@
Version 1.9.2:
- Ursache der Trägheit endlich gefunden. Die erweiterte Messung zeigte: „1 Bereich, 100 %
der Fläche" - LVGL zeichnete bei jeder Zustandsmeldung den kompletten Bildschirm neu, also
einmal pro Sekunde 240 bis 300 Millisekunden lang. Es war also kein Tempoproblem beim
Zeichnen, sondern es wurde viel zu viel als „muss neu gezeichnet werden" gemeldet.
- Grund: LVGL macht den Bereich einer Beschriftung ungültig, sobald ihr Text gesetzt wird -
auch dann, wenn er sich gar nicht geändert hat. Dasselbe gilt für Farbwechsel. Auf dem
Dashboard wurden im Sekundentakt Werte quer über den ganzen Bildschirm gesetzt: Uhrzeit
oben, Temperaturen in der Mitte, Bezugswerte unten. LVGL fasst benachbarte ungültige
Bereiche zu einem zusammen - und der umfasste dann die gesamte Fläche.
- Alle regelmäßig gesetzten Werte werden jetzt vorher verglichen und nur bei echter Änderung
neu geschrieben; für Farben gilt dasselbe. Da sich Temperaturen selten um ein Zehntelgrad
ändern und der Sollwert praktisch nie, entfallen die meisten dieser Neuzeichnungen.
- Das betrifft alle Bildschirmgrößen; auf den JC-Panels war es nur nie störend, weil dort ein
Bildaufbau ein Bruchteil davon kostet.
Version 1.9.1:
- Das direkte Zeichnen in den Bildspeicher aus 1.9.0 ist zurückgenommen: Es hat die
Bildaufbauzeit nicht verkürzt (weiterhin 240 bis 300 ms) und stattdessen Bildfehler
+49 -23
View File
@@ -25,6 +25,7 @@
#include "display_hal.h"
#include "esp_rom_sys.h"
#include "lvgl_private.h"
#include <stdarg.h>
// ------------------------------------------------------------------------------------
// Schriftgroesse
@@ -1377,7 +1378,7 @@ static void flush_targ() {
static void targ_w_set(float v) {
if (v < 0) v = 0; if (v > 135) v = 135;
g_targW = v;
if (lblTargW) lv_label_set_text(lblTargW, tempStr(g_targW, 0).c_str());
if (lblTargW) label_set_if_changed(lblTargW, tempStr(g_targW, 0).c_str());
if (g_arcW) lv_arc_set_value(g_arcW, (int32_t)(g_targW + 0.5f));
g_targWDirty = true;
schedule_targ_send();
@@ -1385,7 +1386,7 @@ static void targ_w_set(float v) {
static void targ_d_set(float v) {
if (v < 0) v = 0; if (v > 200) v = 200;
g_targD = v;
if (lblTargD) lv_label_set_text(lblTargD, tempStr(g_targD, 0).c_str());
if (lblTargD) label_set_if_changed(lblTargD, tempStr(g_targD, 0).c_str());
if (g_arcD) lv_arc_set_value(g_arcD, (int32_t)(g_targD + 0.5f));
g_targDDirty = true;
schedule_targ_send();
@@ -3240,10 +3241,35 @@ static void eco_case_timer_cb(lv_timer_t*) {
// =====================================================================================
// Setzt den Labeltext nur, wenn er sich aendert -> verhindert unnoetige Invalidierung
// (im Full-Refresh-Modus loest jede Aenderung ein komplettes Neuzeichnen aus).
// LVGL macht bei jedem Setzen den Bereich der Beschriftung ungueltig - auch dann, wenn sich
// der Text gar nicht geaendert hat. Auf dem Dashboard werden im Sekundentakt Werte quer ueber
// den ganzen Bildschirm gesetzt; LVGL fasst die ungueltigen Bereiche zu einem zusammen, und
// das ist dann die gesamte Flaeche. Gemessen waren das 240 bis 300 ms pro Sekunde, in denen
// die Oberflaeche mit nichts anderem beschaeftigt war. Deshalb wird ueberall dort, wo sich
// Werte nur selten aendern, vorher verglichen.
static void label_set_if_changed(lv_obj_t* lbl, const char* txt) {
if (!lbl || !txt) return;
const char* cur = lv_label_get_text(lbl);
if (!cur || strcmp(cur, txt) != 0) lv_label_set_text(lbl, txt);
}
// Wie oben, aber mit Formatangabe.
static void label_fmt_if_changed(lv_obj_t* lbl, const char* fmt, ...) {
if (!lbl) return;
char buf[96];
va_list ap; va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
label_set_if_changed(lbl, buf);
}
// Auch ein Farbwechsel macht den Bereich ungueltig. Gleiche Farbe -> nichts tun.
static void label_color_if_changed(lv_obj_t* lbl, lv_color_t col) {
if (!lbl) return;
lv_color_t cur = lv_obj_get_style_text_color(lbl, LV_PART_MAIN);
if (lv_color_to_u32(cur) != lv_color_to_u32(col))
lv_obj_set_style_text_color(lbl, col, 0);
}
// Pre-Infusion-Phasenleiste aktualisieren (100-ms-Takt aus dash_timers_cb).
// Die aktive Phase kommt bevorzugt von der S3 (piState = echte Statemachine, Quelle der
// Wahrheit; Push-Latenz max. 250 ms). Sendet eine aeltere S3-Firmware das Feld noch nicht
@@ -4357,43 +4383,43 @@ void ui_update(const MachineState& st) {
// Dashboard - Temperaturkarten (im Fehlerfall Warnsymbol + Klartext statt Zahl, wie OLED)
if (lblTempW) {
if (st.wasserSafetyShutdown || st.wasserSensorError) {
lv_label_set_text(lblTempW, LV_SYMBOL_WARNING);
lv_obj_set_style_text_color(lblTempW, COL_DANGER, 0);
lv_label_set_text(lblSetW, st.wasserSafetyShutdown ? "Sicherheitsabsch." : "Sensorfehler!");
lv_obj_set_style_text_color(lblSetW, COL_DANGER, 0);
label_set_if_changed(lblTempW, LV_SYMBOL_WARNING);
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 {
lv_label_set_text(lblTempW, tempStr(st.tempW, 1).c_str());
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.
lv_obj_set_style_text_color(lblTempW, temp_value_color(lv_color_hex(0x1976d2)), 0);
label_color_if_changed(lblTempW, temp_value_color(lv_color_hex(0x1976d2)));
label_set_if_changed(lblSetW, "Cold Extraction - Heizen aus");
lv_obj_set_style_text_color(lblSetW, lv_color_hex(0x1976d2), 0);
label_color_if_changed(lblSetW, lv_color_hex(0x1976d2));
temp_progress_set(barDutyW, arcTempW, bulbTempW, 0, lv_color_hex(0x1976d2));
} else {
// Ist-Wert faerbt sich weich Richtung Soll (Blau -> Gold -> Gruen im Zielband)
lv_color_t colW = temp_state_color(st.tempW, st.setW);
lv_obj_set_style_text_color(lblTempW, temp_value_color(colW), 0);
lv_label_set_text(lblSetW, ("Soll " + tempStr(st.setW, 1)).c_str());
lv_obj_set_style_text_color(lblSetW, COL_TEXT_DIM, 0);
label_color_if_changed(lblTempW, temp_value_color(colW));
label_set_if_changed(lblSetW, ("Soll " + tempStr(st.setW, 1)).c_str());
label_color_if_changed(lblSetW, COL_TEXT_DIM);
// Fortschritt = Ist/Soll in %, gedeckelt auf 100 (Ist >= Soll -> voll)
int pctW = (st.setW > 0.0f) ? (int)(st.tempW / st.setW * 100.0f + 0.5f) : 0;
temp_progress_set(barDutyW, arcTempW, bulbTempW, pctW, colW);
}
}
if (st.dampfSafetyShutdown || st.dampfSensorError) {
lv_label_set_text(lblTempD, LV_SYMBOL_WARNING);
lv_obj_set_style_text_color(lblTempD, COL_DANGER, 0);
lv_label_set_text(lblSetD, st.dampfSafetyShutdown ? "Sicherheitsabsch." : "Sensorfehler!");
lv_obj_set_style_text_color(lblSetD, COL_DANGER, 0);
label_set_if_changed(lblTempD, LV_SYMBOL_WARNING);
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 {
lv_label_set_text(lblTempD, tempStr(st.tempD, 0).c_str());
label_set_if_changed(lblTempD, tempStr(st.tempD, 0).c_str());
lv_color_t colD = temp_state_color(st.tempD, st.setD);
lv_obj_set_style_text_color(lblTempD, temp_value_color(colD), 0);
lv_label_set_text(lblSetD, ("Soll " + tempStr(st.setD, 0)).c_str());
lv_obj_set_style_text_color(lblSetD, COL_TEXT_DIM, 0);
label_color_if_changed(lblTempD, temp_value_color(colD));
label_set_if_changed(lblSetD, ("Soll " + tempStr(st.setD, 0)).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);
}
@@ -4542,12 +4568,12 @@ void ui_update(const MachineState& st) {
float wDisp = st.weight;
if (wDisp > -0.05f && wDisp <= 0.0f) wDisp = 0.0f;
if (!st.scaleEnabled) {
lv_label_set_text(lblBezug, "--");
label_set_if_changed(lblBezug, "--");
} else if (bbwShot) {
// Waehrend des Bezugs mit Brew-by-Weight: Ist oben, Ziel darunter (zweizeilig)
lv_label_set_text_fmt(lblBezug, "%.1f g\n/ %.1f g", wDisp, shotTarget);
label_fmt_if_changed(lblBezug, "%.1f g\n/ %.1f g", wDisp, shotTarget);
} else {
lv_label_set_text_fmt(lblBezug, "%.1f g", wDisp);
label_fmt_if_changed(lblBezug, "%.1f g", wDisp);
}
// Fortschrittsbalken Gewicht/Ziel (nur waehrend BBW-Bezug)
if (g_bbwBar) {