Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
326af4288a | ||
|
|
2e8651bc00 | ||
|
|
b13e5fdaf1 | ||
|
|
671a6696ce | ||
|
|
288f61447d | ||
|
|
a91c792491 | ||
|
|
e32c654b8d | ||
|
|
484812cb4c | ||
|
|
ff23e0e614 | ||
|
|
09b5231357 | ||
|
|
16d8465b0c | ||
|
|
407d938031 | ||
|
|
33ce2b3ab9 | ||
|
|
d4d4f2b9d2 | ||
|
|
9fef4f8a6e | ||
|
|
0f5b681421 | ||
|
|
01d960258a | ||
|
|
3c333f0280 | ||
|
|
b6c6214b41 | ||
|
|
63290afa07 | ||
|
|
cf57e11ccb | ||
|
|
6d345d2145 | ||
|
|
a410da6e72 | ||
|
|
f0a214d712 | ||
|
|
21f273aae4 | ||
|
|
e42d2b3de2 | ||
|
|
785f64331d | ||
|
|
40aabe48bc |
@@ -1,3 +1,195 @@
|
||||
Version 1.9.5:
|
||||
- Die festen Breiten aus 1.9.4 haben nicht gereicht: Auch die HÖHE muss festliegen. LVGL
|
||||
prüft beim Setzen eines Textes, ob sich die Eigengröße der Beschriftung geändert haben
|
||||
könnte - ist eines der beiden Maße frei, gilt das Layout des Elternteils als neu zu
|
||||
berechnen, und das macht die ganze Seite ungültig. Temperaturwerte, Sollwerte, die Werte
|
||||
der unteren Zeile und die Uhrzeit in der Kopfleiste haben jetzt feste Maße in beide
|
||||
Richtungen.
|
||||
- Die Uhrzeit hatte denselben Effekt in der Kopfleiste: Beim Wechsel der Minute wurde die
|
||||
Leiste samt Menü-Knopf neu berechnet. Das erklärt den Bereich „x 6..57, y 2..41", der in
|
||||
fast jedem Durchgang auftauchte.
|
||||
- Zusätzlich meldet die Firmware jetzt, wenn der Inhaltsbereich oder die Dashboard-Seite als
|
||||
Ganzes ungültig gemacht wird. Damit ist zu unterscheiden, ob eine einzelne Beschriftung
|
||||
den Bildaufbau auslöst oder gleich der komplette Bereich - zwei verschiedene Ursachen mit
|
||||
verschiedenen Gegenmitteln.
|
||||
|
||||
Version 1.9.4:
|
||||
- Die Messung mit Koordinaten hat den Verursacher benannt: Der ungültige Bereich war
|
||||
„x 0..1279, y 44..719" - also der gesamte Inhalt unterhalb der Kopfleiste, bei jeder
|
||||
Zustandsmeldung.
|
||||
- Grund: Die Anzeigewerte hatten keine feste Breite. Wächst oder schrumpft eine Beschriftung
|
||||
mit ihrem Inhalt - etwa von „92,4 °C" auf „100,1 °C" -, rechnet LVGL das Layout der
|
||||
gesamten Seite neu und macht sie dabei komplett ungültig. Es genügt also nicht, den Text
|
||||
nur bei echter Änderung zu setzen; er darf beim Ändern auch die Breite nicht verändern.
|
||||
- Temperaturwerte, Sollwerte und die Werte der unteren Zeile haben jetzt eine feste Breite
|
||||
mit zentriertem Text. Sichtbar ändert sich dadurch nichts - die Werte standen ohnehin
|
||||
mittig.
|
||||
|
||||
Version 1.9.3:
|
||||
- Fehlerbehebung: Bei geöffnetem Menü war der Seiteninhalt dahinter verschwunden. Der
|
||||
Vorhang des Menüs war in 1.8.7 deckend gemacht worden, um Zeichenlast zu sparen - dabei
|
||||
verdeckt er aber genau das, was sichtbar bleiben soll. Er ist wieder durchscheinend.
|
||||
- Weitere Stellen umgestellt, die bei jeder Zustandsmeldung neu zeichnen ließen, obwohl sich
|
||||
nichts geändert hatte: das Zustandsband der Darstellung „Band" (es spannt sich über die
|
||||
volle Breite und machte damit allein schon einen Streifen über den ganzen Bildschirm
|
||||
ungültig), die Heizleistungsbalken und das WLAN-Symbol.
|
||||
- Die Messung nennt jetzt zusätzlich die Lage der neu gezeichneten Bereiche. Die Größe
|
||||
allein sagt nicht, welcher Teil der Oberfläche das Neuzeichnen auslöst - die Koordinaten
|
||||
schon.
|
||||
|
||||
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
|
||||
erzeugt, weil in denselben Speicher gezeichnet wird, aus dem gerade angezeigt wird.
|
||||
Damit ist auch klar, dass der Flaschenhals nicht im Kopieren liegt, sondern im Zeichnen
|
||||
selbst. Der Schalter WS7_DIRECT_FB bleibt samt Begründung erhalten.
|
||||
- Die Messung sagt jetzt zusätzlich, WIE VIEL neu gezeichnet wurde: Anzahl der Bereiche und
|
||||
ihr Anteil an der Gesamtfläche. Erst damit lässt sich unterscheiden, ob LVGL nur kleine
|
||||
Bereiche anfasst - dann ist das Zeichnen an sich zu langsam - oder ob jedes Mal der ganze
|
||||
Bildschirm neu entsteht, weil ihn etwas unnötig ungültig macht. Das sind zwei völlig
|
||||
verschiedene Ursachen mit verschiedenen Gegenmitteln.
|
||||
|
||||
Version 1.9.0:
|
||||
- Die Messung aus 1.8.8 hat die Ursache der Trägheit gefunden: Jeder Bildaufbau kostete
|
||||
240 bis 300 Millisekunden - dauerhaft, auch im Leerlauf. Fünf Sekunden für den
|
||||
Aufweckdialog waren also rund zwanzig solcher Durchgänge.
|
||||
- Grund war, dass zweimal durch den Bildspeicher gearbeitet wurde: LVGL zeichnete in einen
|
||||
eigenen Puffer, und dessen Inhalt wurde anschließend vollständig in den Bildspeicher
|
||||
kopiert. Bei 1280x720 in 24 Bit sind das zwei Durchgänge durch je 2,7 MB.
|
||||
- LVGL zeichnet jetzt direkt in den Bildspeicher. Der Treiber lässt das Kopieren dann weg,
|
||||
und LVGL zeichnet nur noch die geänderten Bereiche statt jedes Mal die ganze Fläche.
|
||||
Schalter WS7_DIRECT_FB in pins_config.h.
|
||||
- Die Anzeigen von Shot-Timer und Waage bleiben während eines Bezugs in der normalen
|
||||
Schrift; der fette Wechsel ist entfernt. Der Bereich bekommt weiterhin mehr Platz.
|
||||
- Neu: Die Größe der Standby-Uhr lässt sich einstellen (Klein, Mittel, Groß - 120, 180 oder
|
||||
240 Pixel), zu finden auf der Info-Seite unter Darstellung. Anders als die allgemeine
|
||||
Schriftgröße wirkt sie sofort, weil sie an einem einzigen Textfeld hängt.
|
||||
- Die Knöpfe des Aufweckdialogs berücksichtigen jetzt ebenfalls die eingestellte
|
||||
Schriftgröße und wachsen mit.
|
||||
|
||||
Version 1.8.8:
|
||||
- Der Aufweckdialog berücksichtigt jetzt die eingestellte Schriftgröße vollständig: Die
|
||||
Knöpfe bekommen dieselbe Schrift wie der Text und wachsen mit (150 / 210 / 280 Pixel).
|
||||
Bisher erbten sie die kleine Grundschrift und behielten eine feste Breite - neben dem
|
||||
größeren Text wirkten sie dadurch verloren.
|
||||
- Die große Standby-Uhr bleibt bewusst bei ihrer festen Größe: Sie ist bereits auf die
|
||||
Bildschirmhöhe abgestimmt, eine Umschaltung würde sie nur schlechter passend machen.
|
||||
- Neu: eine Zeitmessung für langsame Bildaufbauten. Überschreitet ein Zeichenzyklus
|
||||
200 Millisekunden, meldet die serielle Konsole seine Dauer. Damit lässt sich feststellen,
|
||||
welcher Vorgang tatsächlich Zeit kostet - die bisherigen Erklärungen für die Verzögerung
|
||||
beim Aufweckdialog haben sich als falsch erwiesen, und weiteres Raten hilft nicht.
|
||||
Im Normalbetrieb bleibt die Ausgabe still.
|
||||
|
||||
Version 1.8.7:
|
||||
- Vom Fingertipp auf die Standby-Uhr bis zum Aufweckdialog vergingen rund fünf Sekunden mit
|
||||
schwarzem Bildschirm. Ursache war der halbdurchsichtige Vorhang, den der Dialog über den
|
||||
ganzen Bildschirm legt: Dafür muss LVGL einen Vollbild-Zwischenpuffer anlegen und die
|
||||
gesamte Fläche mischen - bei 1280x720 in 24 Bit die teuerste Operation überhaupt.
|
||||
- Der Vorhang ist auf dem Waveshare-Panel jetzt deckend. Verloren geht dabei nichts: Im
|
||||
Standby liegt dahinter ohnehin nur Schwarz.
|
||||
- Aus demselben Grund ist auch der Vorhang des Menüs deckend, in einem dunklen Ton. Auf
|
||||
dunklem Grund sieht das praktisch genauso aus wie halbdurchsichtig, spart aber dieselbe
|
||||
Mischarbeit. Die JC-Panels behalten in beiden Fällen die Transparenz.
|
||||
- Der Aufweckdialog wird mit größerer Schrift auch breiter (420 / 640 / 900 Pixel), damit
|
||||
der Text nicht an unglücklichen Stellen umbricht.
|
||||
|
||||
Version 1.8.6:
|
||||
- Die Uhrzeit verschwindet nicht mehr, wenn die Hauptplatine ihre Zeit gerade neu abgleicht.
|
||||
Der P4 merkt sich die zuletzt empfangene Uhrzeit und zählt zwischen den Meldungen selbst
|
||||
weiter; sobald die Hauptplatine wieder eine Zeit schickt, gilt deren Wert.
|
||||
- Der P4 stellt dabei nichts richtig und ersetzt keine Zeitquelle - er überbrückt nur die
|
||||
Lücke. Hat er noch nie eine Uhrzeit bekommen, bleibt die Anzeige wie bisher leer.
|
||||
- Gilt für die Uhr in der Kopfleiste und für die große Standby-Uhr.
|
||||
|
||||
Version 1.8.5:
|
||||
- Die Verzögerung von rund zwei Sekunden rund um den Standby ist behoben. Sie lag nicht an
|
||||
der Helligkeitsumschaltung, sondern daran, dass die Standby-Uhr bei JEDER Zustandsmeldung
|
||||
der Hauptplatine nach vorn geholt wurde. Das ordnet die Ebenen neu und lässt LVGL den
|
||||
gesamten Bildschirm neu zeichnen - einmal pro Sekunde, jedes Mal 1280x720 in 24 Bit.
|
||||
Dadurch hing die Oberfläche dauerhaft ein bis zwei Sekunden hinterher. Nach vorn geholt
|
||||
wird die Uhr jetzt beim Einblenden, danach bleibt sie dort.
|
||||
- Der Aufweckdialog schließt sich nach 30 Sekunden ohne Auswahl von selbst und wird wie
|
||||
„Abbrechen" gewertet. Vorher blieb das Display hell stehen - der Dialog schaltet auf
|
||||
normale Helligkeit, und ein versehentlicher Tipp auf die Uhr hätte die Maschine die ganze
|
||||
Nacht leuchten lassen.
|
||||
- Die Verlaufskurve hat keinen eigenen Hintergrund mehr; sie liegt jetzt direkt auf der
|
||||
Karte. Die abgesetzte Fläche war nur nötig, solange die Kurve vor dem ersten Messwert
|
||||
leer blieb - seit sie beim aktuellen Wert startet, ist immer eine Linie da.
|
||||
- Auf der Statistikseite erschien ein leeres Rechteck, wenn ohne Waage bezogen wurde. Dort
|
||||
stand ein typografischer Gedankenstrich, den der Zeichensatz der Schrift nicht enthält.
|
||||
Jetzt ein einfacher Bindestrich.
|
||||
|
||||
Version 1.8.4:
|
||||
- Standby-Uhr und Aufweckdialog erscheinen jetzt sofort statt mit spürbarer Verzögerung.
|
||||
Ursache waren die weichen Ein- und Ausblendungen: Eine Deckkraft-Animation ist bei einem
|
||||
Bildspeicher in RGB888 das Teuerste, was die Oberfläche tun kann, weil für jedes Einzelbild
|
||||
die gesamte Fläche unter der Ebene neu gezeichnet und mit ihr gemischt werden muss. Auf
|
||||
1280x720 dauerte eine solche Blende deutlich länger als die 450 ms, die vorgesehen waren.
|
||||
Auf dem Waveshare-Panel laufen die Übergänge deshalb ohne Animation; die JC-Panels
|
||||
behalten sie.
|
||||
- Beim Wechsel der Schriftgröße blitzte kurz ein blaues Bild auf. Das kommt nicht von der
|
||||
Firmware: Das Panel zeigt von sich aus Blau, sobald kein Videosignal anliegt - und beim
|
||||
nötigen Neustart reißt das Signal kurz ab. Die Beleuchtung wird jetzt vorher abgeschaltet;
|
||||
dunkel ist unauffälliger als blau.
|
||||
- Neue Einstellung unter Darstellung: „Temperaturzahl in der Zustandsfarbe". Aus bleibt die
|
||||
Zahl weiß, die Zustandsfarbe steckt dann allein im Balken darunter - ruhiger, und die Zahl
|
||||
bleibt eine Zahl. Ein bedeutet, sie färbt sich mit, wie bisher. Vorgabe ist aus.
|
||||
Warnungen bleiben in jedem Fall rot; das ist keine Gestaltung, sondern eine Meldung.
|
||||
|
||||
Version 1.8.3:
|
||||
- Fehlerbehebung: Bei allen Darstellungen stand hinter der Temperatur ein leeres Rechteck
|
||||
statt des C. Die beiden großen Schriften waren auf Ziffern, Komma, Punkt, Minus und
|
||||
Gradzeichen beschränkt - das C fehlte darin schlicht. Es ist jetzt enthalten, ebenso g
|
||||
und s für die Gewichts- und Zeitanzeige während eines Bezugs.
|
||||
- Fehlerbehebung: Bei der Darstellung „Verlauf" war keine Kurve zu sehen. Ein frisch
|
||||
angelegtes Diagramm enthält lauter „kein Wert"-Punkte und zeichnet deshalb nichts; ohne
|
||||
Hintergrund war der Bereich vollständig unsichtbar, bis nach einer Minute genug Messwerte
|
||||
zusammengekommen wären. Die Kurve startet jetzt beim aktuellen Messwert, und ihre Fläche
|
||||
ist leicht abgesetzt.
|
||||
- Die großen Zahlen des Dashboards stehen jetzt im fetten Schnitt und etwas enger, wie im
|
||||
Entwurf. Erzeugt aus der variablen Maven-Pro-Datei mit Gewicht 700 - dieselbe Schriftart
|
||||
wie bisher, nur kräftiger. Die übrige Oberfläche bleibt unverändert.
|
||||
|
||||
Version 1.8.2:
|
||||
- Die Liste der letzten Bezüge auf der Statistikseite ist jetzt eine Tabelle mit festen
|
||||
Spalten: Zeitpunkt, Dauer, Gewicht und gegebenenfalls die Kennzeichnung „kalt".
|
||||
- Vorher war es ein einziges mehrzeiliges Textfeld, in dem die Werte durch Leerzeichen
|
||||
getrennt aneinandergereiht standen. Weil Zahlen unterschiedlich breit sind, stand nichts
|
||||
untereinander - zwei Bezüge ließen sich nicht vergleichen, ohne jede Zeile einzeln zu
|
||||
lesen. Genau das sah unruhig aus.
|
||||
- Ziffern haben jetzt gleiche Breite, jede zweite Zeile ist leicht abgesetzt, und eine
|
||||
Kopfzeile benennt die Spalten. Wurde ohne Waage bezogen, bleibt die Gewichtsspalte leer,
|
||||
statt eine Null vorzutäuschen.
|
||||
- Die Zeilen werden einmal angelegt und danach nur gefüllt oder ausgeblendet, statt bei
|
||||
jeder Aktualisierung neu zu entstehen.
|
||||
|
||||
Version 1.8.1:
|
||||
- Die Bezugsanzeige ist da, und zwar für alle vier Darstellungen gemeinsam: Sobald ein
|
||||
Bezug oder Dampfbezug läuft, schrumpft der Temperaturbereich und die untere Zeile wächst.
|
||||
Shot-Timer und Gewicht werden dabei auf 96 Pixel vergrößert - während eines Bezugs zählen
|
||||
Zeit und Gewicht, die Kesseltemperaturen sind dann Nebensache.
|
||||
- Es gibt keine zweite Ansicht: Die Seite verschiebt nur das Gewicht zwischen ihren beiden
|
||||
Bereichen. Damit bleibt alles an seinem Platz, und es gibt nichts doppelt zu pflegen.
|
||||
- Der Wechsel hält nach dem Bezug noch vier Sekunden an. Ohne dieses Nachhalten würde die
|
||||
Seite bei jeder kurzen Unterbrechung hin- und herspringen - das wäre unruhiger als gar
|
||||
kein Wechsel.
|
||||
|
||||
Version 1.8.0:
|
||||
- Das Dashboard hat vier neue Darstellungen, die die bisherigen fünf ersetzen. Sie sind
|
||||
keine Varianten desselben Bildes, sondern vier Antworten auf die Frage, was die große
|
||||
|
||||
@@ -567,8 +567,10 @@
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_28) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_40) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_48) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_96) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_140) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_bold_96) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_bold_140) \
|
||||
LV_FONT_DECLARE(lv_font_clock_120) \
|
||||
LV_FONT_DECLARE(lv_font_clock_180) \
|
||||
LV_FONT_DECLARE(lv_font_clock_240)
|
||||
|
||||
/*Always set a default font*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -496,6 +496,32 @@ static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
|
||||
// buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), MALLOC_CAP_DMA);
|
||||
assert(buf1);
|
||||
ESP_LOGI(TAG, "LVGL buffer size: %dKB", buffer_size * sizeof(lv_color_t) / 1024);
|
||||
|
||||
#if WS7_DIRECT_FB
|
||||
// PROJEKTAENDERUNG: LVGL zeichnet direkt in den Bildspeicher.
|
||||
//
|
||||
// Der Hersteller-Port legt einen eigenen Zeichenpuffer an; der fertige Inhalt wird
|
||||
// danach in den Bildspeicher kopiert. Bei 1280x720 in 24 Bit sind das zwei Durchgaenge
|
||||
// durch je 2,7 MB PSRAM - gemessen rund 250 bis 300 ms pro Bild, und das dauerhaft.
|
||||
// Genau deshalb hing die Oberflaeche eine gute Viertelsekunde hinterher, und Vorgaenge
|
||||
// mit mehreren Bildern brauchten Sekunden.
|
||||
//
|
||||
// Ist der Zeichenpuffer derselbe Speicher wie der Bildspeicher, laesst der
|
||||
// esp_lcd-Treiber das Kopieren weg und schreibt nur den Prozessor-Cache zurueck. Dazu
|
||||
// kommt LV_DISPLAY_RENDER_MODE_DIRECT: LVGL zeichnet dann nur die geaenderten Bereiche
|
||||
// neu, statt jedes Mal die ganze Flaeche.
|
||||
//
|
||||
// Preis: Gezeichnet wird in den Speicher, der gerade angezeigt wird - bei schnellen
|
||||
// Wechseln kann kurz eine Kante durchs Bild laufen. Mit nur einem Bildspeicher (den das
|
||||
// Panel verlangt, siehe WS7_SINGLE_FB) ist das ohnehin nicht zu vermeiden.
|
||||
void* fb0 = NULL;
|
||||
if (esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb0) == ESP_OK && fb0) {
|
||||
buf1 = fb0;
|
||||
buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES;
|
||||
ESP_LOGI(TAG, "LVGL zeichnet direkt in den Bildspeicher (%dKB)",
|
||||
(int)(buffer_size * sizeof(lv_color_t) / 1024));
|
||||
}
|
||||
#endif
|
||||
#endif /* LVGL_PORT_AVOID_TEAR_ENABLE */
|
||||
|
||||
ESP_LOGD(TAG, "Register display driver to LVGL");
|
||||
@@ -511,7 +537,7 @@ static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle)
|
||||
display, buf1, buf2, buffer_size * sizeof(lv_color_t),
|
||||
#if LVGL_PORT_FULL_REFRESH
|
||||
LV_DISPLAY_RENDER_MODE_FULL
|
||||
#elif LVGL_PORT_DIRECT_MODE
|
||||
#elif LVGL_PORT_DIRECT_MODE || WS7_DIRECT_FB
|
||||
LV_DISPLAY_RENDER_MODE_DIRECT
|
||||
#else
|
||||
LV_DISPLAY_RENDER_MODE_PARTIAL
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
// nicht - siehe die ausfuehrliche Begruendung weiter unten beim Avoid-Tear-Schalter.
|
||||
// Auf 0 nur, wenn das Startverhalten erneut untersucht wird.
|
||||
#define WS7_SINGLE_FB 1
|
||||
// LVGL zeichnet direkt in den Bildspeicher statt in einen eigenen Puffer, dessen Inhalt
|
||||
// danach kopiert wird. GEPRUEFT: bringt an diesem Panel nichts (die Bildaufbauzeit blieb bei
|
||||
// 240-300 ms) und erzeugt Bildfehler, weil in denselben Speicher gezeichnet wird, aus dem
|
||||
// gerade angezeigt wird. Der Flaschenhals liegt also nicht im Kopieren, sondern im Zeichnen
|
||||
// selbst. Bleibt als Schalter erhalten, damit der Versuch nicht erneut gemacht wird.
|
||||
#define WS7_DIRECT_FB 0
|
||||
#define WS7_PARALLEL_RENDER 1
|
||||
|
||||
#define EXAMPLE_LVGL_PORT_TASK_MAX_DELAY_MS 500 //range 2 to 2000
|
||||
|
||||
+541
-76
@@ -22,6 +22,10 @@
|
||||
#include "ui.h"
|
||||
#include <Preferences.h>
|
||||
#include "esp_system.h"
|
||||
#include "display_hal.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "lvgl_private.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Schriftgroesse
|
||||
@@ -82,6 +86,10 @@ static void font_step_save_and_restart(uint8_t step) {
|
||||
}
|
||||
// Der Zaehler der Absturzsicherung darf diesen gewollten Neustart nicht mitzaehlen.
|
||||
{ Preferences b; if (b.begin("bootguard", false)) { b.putUShort("early", 0); b.end(); } }
|
||||
// Beleuchtung aus, bevor der Neustart die Bildausgabe abreissen laesst. Ohne Signal
|
||||
// zeigt das Panel von sich aus ein blaues Bild - das blitzte beim Wechsel der
|
||||
// Schriftgroesse kurz auf. Dunkel ist unauffaelliger als blau.
|
||||
hal_backlight(0);
|
||||
delay(120);
|
||||
esp_restart();
|
||||
}
|
||||
@@ -168,6 +176,7 @@ static void label_set_if_changed(lv_obj_t* lbl, const char* txt); // Definitio
|
||||
// TD_THERMO - vertikale Thermometer-Saeulen mit Kolben und Glanzstreifen
|
||||
enum TempDesign { TD_GEWICHTET = 0, TD_VERLAUF, TD_FLAECHEN, TD_BAND, TD_DESIGN_COUNT };
|
||||
static uint8_t g_tempDesign = TD_BAND;
|
||||
static bool g_tempColored = false; // Temperaturzahl in der Zustandsfarbe statt weiss
|
||||
#define DASH_SPARK_POINTS 60 // eine Minute Verlauf, ein Punkt je Sekunde
|
||||
static lv_obj_t* g_sparkW = nullptr; // Verlaufskurven der Darstellung TD_VERLAUF
|
||||
static lv_obj_t* g_sparkD = nullptr;
|
||||
@@ -189,6 +198,8 @@ static lv_obj_t* g_profChip = nullptr;
|
||||
static lv_obj_t* g_profPicker = nullptr; // Schnellwahl-Overlay (Vollbild-Abdunkelung)
|
||||
static lv_obj_t* g_profPickerList = nullptr; // Button-Liste im Overlay
|
||||
static lv_obj_t* g_profPickerInfo = nullptr; // "Lade Profile..."-Hinweis im Overlay
|
||||
static lv_obj_t* g_infoRow = nullptr; // untere Zeile; waechst waehrend eines Bezugs
|
||||
static bool g_dashBrewMode = false;
|
||||
static lv_obj_t* cellShot; static lv_obj_t* lblShotTimer; static lv_obj_t* g_bbtBar = nullptr;
|
||||
static lv_obj_t* cellSteam; static lv_obj_t* lblSteamTimer; static lv_obj_t* g_sbtBar = nullptr;
|
||||
static lv_obj_t* cellBezug; static lv_obj_t* lblBezug; static lv_obj_t* g_bbwBar = nullptr;
|
||||
@@ -355,7 +366,6 @@ static String g_selProfile;
|
||||
|
||||
// Statistik (Nutzungsverlauf)
|
||||
static lv_obj_t* g_statSummary = nullptr; // Aggregate (Heute/Woche/Gesamt)
|
||||
static lv_obj_t* g_statList = nullptr; // Liste der letzten Einzelbezuege
|
||||
|
||||
// =====================================================================================
|
||||
// Hilfen
|
||||
@@ -574,6 +584,132 @@ static void grid_at(lv_obj_t* c, uint8_t col, uint8_t row, uint8_t col_span = 1)
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Liste der letzten Bezuege
|
||||
//
|
||||
// Frueher war das EIN mehrzeiliges Textfeld: Zeitpunkt, Dauer und Gewicht standen in einer
|
||||
// Zeile aneinandergereiht, durch Leerzeichen getrennt. Weil die Zahlen unterschiedlich
|
||||
// breit sind, stand nichts untereinander - man konnte zwei Bezuege nicht vergleichen, ohne
|
||||
// jede Zeile einzeln zu lesen.
|
||||
//
|
||||
// Jetzt eine Tabelle mit festen Spalten und Ziffern gleicher Breite. Die Zeilen werden
|
||||
// einmal angelegt und danach nur noch gefuellt oder ausgeblendet - eine Statistikseite,
|
||||
// die bei jeder Aktualisierung Dutzende Objekte neu anlegt, waere unnoetig teuer.
|
||||
// ------------------------------------------------------------------------------------
|
||||
#define STAT_ROW_COUNT 12
|
||||
|
||||
static lv_obj_t* g_statRow[STAT_ROW_COUNT] = {};
|
||||
static lv_obj_t* g_statTime[STAT_ROW_COUNT] = {};
|
||||
static lv_obj_t* g_statDur[STAT_ROW_COUNT] = {};
|
||||
static lv_obj_t* g_statWgt[STAT_ROW_COUNT] = {};
|
||||
static lv_obj_t* g_statCold[STAT_ROW_COUNT] = {};
|
||||
static lv_obj_t* g_statEmpty = nullptr;
|
||||
|
||||
static lv_obj_t* stat_cell(lv_obj_t* row, int32_t width, bool rechts, lv_color_t col) {
|
||||
lv_obj_t* l = lv_label_create(row);
|
||||
if (width > 0) lv_obj_set_width(l, width);
|
||||
else lv_obj_set_flex_grow(l, 1);
|
||||
lv_obj_set_style_text_color(l, col, 0);
|
||||
lv_obj_set_style_text_align(l, rechts ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0);
|
||||
lv_label_set_text(l, "");
|
||||
return l;
|
||||
}
|
||||
|
||||
static void stat_list_build(lv_obj_t* parent) {
|
||||
lv_obj_t* card = th_card(parent);
|
||||
lv_obj_set_width(card, LV_PCT(100));
|
||||
lv_obj_set_height(card, LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(card, 0, 0);
|
||||
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
// Kopfzeile - benennt die Spalten, damit die Zahlen ohne Einheit auskommen
|
||||
lv_obj_t* head = lv_obj_create(card);
|
||||
lv_obj_set_width(head, LV_PCT(100));
|
||||
lv_obj_set_height(head, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_bg_opa(head, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(head, 0, 0);
|
||||
lv_obj_set_style_pad_all(head, 4, 0);
|
||||
lv_obj_set_style_pad_column(head, 10, 0);
|
||||
lv_obj_set_flex_flow(head, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_clear_flag(head, LV_OBJ_FLAG_SCROLLABLE);
|
||||
stat_cell(head, 0, false, COL_TEXT_MUT);
|
||||
lv_label_set_text(lv_obj_get_child(head, 0), "Zeitpunkt");
|
||||
lv_label_set_text(stat_cell(head, 96, true, COL_TEXT_MUT), "Dauer");
|
||||
lv_label_set_text(stat_cell(head, 96, true, COL_TEXT_MUT), "Gewicht");
|
||||
lv_label_set_text(stat_cell(head, 58, true, COL_TEXT_MUT), "");
|
||||
|
||||
for (int i = 0; i < STAT_ROW_COUNT; i++) {
|
||||
lv_obj_t* r = lv_obj_create(card);
|
||||
lv_obj_set_width(r, LV_PCT(100));
|
||||
lv_obj_set_height(r, LV_SIZE_CONTENT);
|
||||
// Jede zweite Zeile leicht abgesetzt - fuehrt das Auge ueber die Zeile, ohne dass
|
||||
// dafuer Trennlinien noetig waeren.
|
||||
lv_obj_set_style_bg_color(r, lv_color_hex(0x242424), 0);
|
||||
lv_obj_set_style_bg_opa(r, (i % 2) ? LV_OPA_COVER : LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(r, 0, 0);
|
||||
lv_obj_set_style_radius(r, 5, 0);
|
||||
lv_obj_set_style_pad_all(r, 5, 0);
|
||||
lv_obj_set_style_pad_column(r, 10, 0);
|
||||
lv_obj_set_flex_flow(r, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(r, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(r, LV_OBJ_FLAG_SCROLLABLE);
|
||||
g_statTime[i] = stat_cell(r, 0, false, COL_TEXT_DIM);
|
||||
g_statDur[i] = stat_cell(r, 96, true, COL_TEXT);
|
||||
g_statWgt[i] = stat_cell(r, 96, true, COL_TEXT);
|
||||
g_statCold[i] = stat_cell(r, 58, true, lv_color_hex(0x80cbc4));
|
||||
g_statRow[i] = r;
|
||||
lv_obj_add_flag(r, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
g_statEmpty = lv_label_create(card);
|
||||
lv_obj_set_width(g_statEmpty, LV_PCT(100));
|
||||
lv_label_set_text(g_statEmpty, "Noch keine Bezüge aufgezeichnet.");
|
||||
lv_obj_set_style_text_color(g_statEmpty, COL_TEXT_MUT, 0);
|
||||
lv_obj_set_style_pad_all(g_statEmpty, 5, 0);
|
||||
}
|
||||
|
||||
// Eine Zeile fuellen. 'w' unter 0 bedeutet: ohne Waage bezogen, dann bleibt die Spalte leer
|
||||
// statt eine Null vorzutaeuschen.
|
||||
static void stat_list_set(int i, const char* dt, float d, float w, bool kalt) {
|
||||
if (i < 0 || i >= STAT_ROW_COUNT || !g_statRow[i]) return;
|
||||
lv_obj_remove_flag(g_statRow[i], LV_OBJ_FLAG_HIDDEN);
|
||||
lv_label_set_text(g_statTime[i], dt);
|
||||
lv_label_set_text_fmt(g_statDur[i], "%.1f s", d);
|
||||
if (w >= 0.0f) lv_label_set_text_fmt(g_statWgt[i], "%.1f g", w);
|
||||
// Bewusst ein einfacher Bindestrich: Der typografisch schoenere Gedankenstrich
|
||||
// fehlt im Zeichensatz der Schrift und erschiene als leeres Rechteck.
|
||||
else lv_label_set_text(g_statWgt[i], "-");
|
||||
lv_label_set_text(g_statCold[i], kalt ? "kalt" : "");
|
||||
}
|
||||
|
||||
static void stat_list_finish(int used) {
|
||||
for (int i = used; i < STAT_ROW_COUNT; i++)
|
||||
if (g_statRow[i]) lv_obj_add_flag(g_statRow[i], LV_OBJ_FLAG_HIDDEN);
|
||||
if (g_statEmpty) {
|
||||
if (used > 0) lv_obj_add_flag(g_statEmpty, LV_OBJ_FLAG_HIDDEN);
|
||||
else lv_obj_remove_flag(g_statEmpty, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Groesse der Standby-Uhr. Anders als die allgemeine Schriftgroesse braucht diese keinen
|
||||
// Neustart: Es haengt an genau EINEM Textfeld, das sich sofort umstellen laesst.
|
||||
enum { UHR_KLEIN = 0, UHR_MITTEL = 1, UHR_GROSS = 2 };
|
||||
static uint8_t g_clockSize = UHR_GROSS;
|
||||
static lv_obj_t* g_clockSizeBtn[3] = {};
|
||||
|
||||
static const lv_font_t* clock_font() {
|
||||
switch (g_clockSize) {
|
||||
case UHR_KLEIN: return &lv_font_clock_120;
|
||||
case UHR_MITTEL: return &lv_font_clock_180;
|
||||
default: return &lv_font_clock_240;
|
||||
}
|
||||
}
|
||||
|
||||
static void clock_size_apply() {
|
||||
if (g_standbyClock) lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0);
|
||||
}
|
||||
|
||||
static void scroll_ohne_nachlauf(lv_obj_t* o) {
|
||||
#if JC_PANEL_TYPE == WS_PANEL_7H
|
||||
lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLL_MOMENTUM);
|
||||
@@ -663,7 +799,19 @@ static void err_icon_cb(lv_event_t*) { show_page(PG_DASH); } // Tipp auf Fehl
|
||||
// Bewusst ueber das 'opa'-Style (faellt via opa_recursive auf die Kinder durch) und NICHT
|
||||
// ueber 'opa_layered' - Letzteres wuerde einen Vollbild-Zwischenpuffer allozieren
|
||||
// und koennte den LVGL-Speicherpool sprengen. So bleibt das Fade speicher-neutral.
|
||||
// Dauer der weichen Uebergaenge (Standby-Uhr, Aufweckdialog, Bezugsuebersicht).
|
||||
//
|
||||
// Auf dem Waveshare-Panel: 0, also ohne Animation. Eine Deckkraft-Animation ist bei einem
|
||||
// Bildspeicher in RGB888 das Teuerste, was die Oberflaeche tun kann - fuer jedes Einzelbild
|
||||
// muss die gesamte Flaeche unter der Ebene neu gezeichnet und mit ihr gemischt werden. Auf
|
||||
// 1280x720 dauert eine solche Blende deutlich laenger als die 450 ms, die sie dauern soll;
|
||||
// spuerbar war das als Verzoegerung, bis die Standby-Uhr erschien und bis nach dem
|
||||
// Fingertipp der Aufweckdialog kam. Ohne Animation erscheinen beide sofort.
|
||||
#if JC_PANEL_TYPE == WS_PANEL_7H
|
||||
static const uint32_t UI_FADE_MS = 0;
|
||||
#else
|
||||
static const uint32_t UI_FADE_MS = 450; // ruhige, unaufdringliche Dauer
|
||||
#endif
|
||||
|
||||
static void ui_fade_exec_cb(void* obj, int32_t v) {
|
||||
lv_obj_set_style_opa((lv_obj_t*)obj, (lv_opa_t)v, 0);
|
||||
@@ -678,6 +826,12 @@ static void ui_fade_hide_done_cb(lv_anim_t* a) {
|
||||
// Blendet 'obj' weich ein (sichtbar schalten, Opazitaet 0 -> voll).
|
||||
static void ui_fade_in(lv_obj_t* obj, uint32_t ms) {
|
||||
if (!obj) return;
|
||||
if (ms == 0) { // ohne Animation: sofort sichtbar
|
||||
lv_anim_delete(obj, ui_fade_exec_cb);
|
||||
lv_obj_set_style_opa(obj, LV_OPA_COVER, 0);
|
||||
lv_obj_remove_flag(obj, LV_OBJ_FLAG_HIDDEN);
|
||||
return;
|
||||
}
|
||||
lv_anim_delete(obj, ui_fade_exec_cb); // evtl. laufende Ausblendung abbrechen
|
||||
lv_obj_set_style_opa(obj, LV_OPA_TRANSP, 0);
|
||||
lv_obj_remove_flag(obj, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -693,6 +847,11 @@ static void ui_fade_in(lv_obj_t* obj, uint32_t ms) {
|
||||
static void ui_fade_out_hide(lv_obj_t* obj, uint32_t ms) {
|
||||
if (!obj || lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return;
|
||||
lv_anim_delete(obj, ui_fade_exec_cb);
|
||||
if (ms == 0) { // ohne Animation: sofort weg
|
||||
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_style_opa(obj, LV_OPA_COVER, 0);
|
||||
return;
|
||||
}
|
||||
lv_anim_t a; lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, obj);
|
||||
lv_anim_set_exec_cb(&a, ui_fade_exec_cb);
|
||||
@@ -726,6 +885,18 @@ static void ui_fade_out_delete(lv_obj_t* obj, uint32_t ms) {
|
||||
static int standby_backlight() {
|
||||
return g_state.standbyShowClock ? (int)g_state.backlightStandbyClock : 0;
|
||||
}
|
||||
// Der Aufweckdialog schliesst sich von selbst, wenn niemand entscheidet. Sonst bliebe das
|
||||
// Display hell stehen, weil der Dialog auf normale Helligkeit schaltet - jemand tippt im
|
||||
// Vorbeigehen versehentlich auf die Uhr, und die Maschine leuchtet die ganze Nacht.
|
||||
// Zeitablauf wird wie "Abbrechen" gewertet: zurueck in die Standby-Helligkeit.
|
||||
#define WAKE_DIALOG_TIMEOUT_MS 30000
|
||||
static lv_timer_t* g_wakeTimeout = nullptr;
|
||||
|
||||
static void wake_timeout_stop() {
|
||||
if (g_wakeTimeout) { lv_timer_delete(g_wakeTimeout); g_wakeTimeout = nullptr; }
|
||||
}
|
||||
static void wake_timeout_cb(lv_timer_t* t); // weiter unten, braucht wake_cancel()
|
||||
|
||||
static void wake_confirm_show() {
|
||||
if (!g_wakeConfirm) return;
|
||||
g_wakeDialogOpen = true;
|
||||
@@ -735,6 +906,9 @@ static void wake_confirm_show() {
|
||||
hal_backlight(bl);
|
||||
lv_obj_remove_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_move_foreground(g_wakeConfirm);
|
||||
wake_timeout_stop();
|
||||
g_wakeTimeout = lv_timer_create(wake_timeout_cb, WAKE_DIALOG_TIMEOUT_MS, nullptr);
|
||||
lv_timer_set_repeat_count(g_wakeTimeout, 1);
|
||||
}
|
||||
static void wake_confirm_hide() { if (g_wakeConfirm) lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN); }
|
||||
// Einzige Quelle der Wahrheit dafuer, ob der Aufweck-Dialog die Auswahl "Espresso oder Cold
|
||||
@@ -748,12 +922,19 @@ static bool wake_choice_offered() {
|
||||
(g_state.cxEnabled && g_state.cxArmable && g_state.cxAskOnWake);
|
||||
}
|
||||
static void standby_screen_cb(lv_event_t*) { wake_confirm_show(); } // Tipp auf Standby-Uhr -> nachfragen
|
||||
static void wake_no_cb(lv_event_t*) { // Abbrechen -> zurueck in die Standby-Helligkeit
|
||||
static void wake_cancel() { // Abbrechen -> zurueck in die Standby-Helligkeit
|
||||
wake_timeout_stop();
|
||||
wake_confirm_hide();
|
||||
g_wakeDialogOpen = false;
|
||||
if (g_state.standbyActive) hal_backlight(standby_backlight());
|
||||
}
|
||||
static void wake_no_cb(lv_event_t*) { wake_cancel(); }
|
||||
static void wake_timeout_cb(lv_timer_t*) { // niemand hat entschieden -> wie Abbrechen
|
||||
g_wakeTimeout = nullptr; // Timer laeuft nur einmal, loescht sich selbst
|
||||
wake_cancel();
|
||||
}
|
||||
static void wake_yes_cb(lv_event_t*) { // Aufwecken -> hell lassen, S3 beendet den Standby
|
||||
wake_timeout_stop();
|
||||
wake_confirm_hide();
|
||||
// g_wakeDialogOpen bleibt true, bis der Standby wirklich endet (ui_update) - sonst wuerde
|
||||
// das Display in der Uebergangsphase kurz auf Standby-Helligkeit abdunkeln.
|
||||
@@ -769,6 +950,7 @@ static void wake_yes_cb(lv_event_t*) { // Aufwecken -> hell lassen, S3 bee
|
||||
// stellen, dann den Standby beenden - sonst heizt der Wasserkreis in der Zwischenzeit an,
|
||||
// und genau das soll die Funktion ja verhindern.
|
||||
static void wake_cold_cb(lv_event_t*) {
|
||||
wake_timeout_stop();
|
||||
wake_confirm_hide();
|
||||
if (g_client) {
|
||||
g_client->sendAction("startColdExtraction");
|
||||
@@ -804,8 +986,11 @@ static lv_color_t temp_state_color(float temp, float set) {
|
||||
static void temp_progress_set(lv_obj_t* bar, lv_obj_t* arc, lv_obj_t* bulb, int pct, lv_color_t col) {
|
||||
if (pct > 100) pct = 100; else if (pct < 0) pct = 0;
|
||||
if (bar) {
|
||||
lv_bar_set_value(bar, pct, LV_ANIM_OFF);
|
||||
lv_obj_set_style_bg_color(bar, col, LV_PART_INDICATOR);
|
||||
// Wert und Farbe nur bei echter Aenderung setzen - beides macht den Balken sonst
|
||||
// bei jeder Zustandsmeldung ungueltig, obwohl sich meist nichts bewegt hat.
|
||||
if (lv_bar_get_value(bar) != pct) lv_bar_set_value(bar, pct, LV_ANIM_OFF);
|
||||
if (lv_color_to_u32(lv_obj_get_style_bg_color(bar, LV_PART_INDICATOR)) != lv_color_to_u32(col))
|
||||
lv_obj_set_style_bg_color(bar, col, LV_PART_INDICATOR);
|
||||
}
|
||||
if (arc) {
|
||||
lv_arc_set_value(arc, pct);
|
||||
@@ -813,6 +998,18 @@ static void temp_progress_set(lv_obj_t* bar, lv_obj_t* arc, lv_obj_t* bulb, int
|
||||
}
|
||||
if (bulb) lv_obj_set_style_bg_color(bulb, col, 0);
|
||||
}
|
||||
|
||||
// Farbe der grossen Temperaturzahl.
|
||||
//
|
||||
// Die Zustandsfarbe steckt ohnehin schon im Balken darunter. Ob die Zahl selbst mitgefaerbt
|
||||
// wird, ist deshalb Geschmackssache: Eingefaerbt ist der Zustand auch aus dem Augenwinkel
|
||||
// erkennbar, weiss wirkt ruhiger und laesst die Zahl als Zahl stehen.
|
||||
//
|
||||
// Warnungen bleiben immer rot - das ist keine Gestaltung, sondern eine Meldung.
|
||||
static lv_color_t temp_value_color(lv_color_t zustand) {
|
||||
return g_tempColored ? zustand : COL_TEXT;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Temperaturbereich des Dashboards - vier Darstellungen
|
||||
//
|
||||
@@ -859,11 +1056,30 @@ static lv_obj_t* tempField(lv_obj_t* parent, const char* title, lv_color_t accen
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, valueFont, 0);
|
||||
// Feste Breite, Text zentriert. Waechst oder schrumpft eine Beschriftung mit ihrem
|
||||
// Inhalt - etwa von "92,4 °C" auf "100,1 °C" -, rechnet LVGL das Flex-Layout der
|
||||
// gesamten Seite neu und macht sie damit komplett ungueltig. Genau das war im
|
||||
// Messprotokoll als "x 0..1279, y 44..719" zu sehen: der ganze Inhaltsbereich, bei
|
||||
// jeder Zustandsmeldung. Mit fester Breite bleibt nur die Beschriftung selbst ungueltig.
|
||||
lv_obj_set_width(*big, LV_PCT(100));
|
||||
lv_obj_set_style_text_align(*big, LV_TEXT_ALIGN_CENTER, 0);
|
||||
// Auch die HOEHE muss fest sein. LVGL prueft beim Setzen eines Textes, ob sich die
|
||||
// Eigengroesse der Beschriftung geaendert haben koennte - ist eines der beiden Masse
|
||||
// frei, gilt das Layout des Elternteils als neu zu berechnen, und das macht die ganze
|
||||
// Seite ungueltig. Erst mit festen Massen bleibt nur die Beschriftung selbst ungueltig.
|
||||
lv_obj_set_height(*big, lv_font_get_line_height(valueFont) + 6);
|
||||
// Grosse Zahlen etwas enger setzen. Bei dieser Groesse wirken die Werksabstaende
|
||||
// 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);
|
||||
|
||||
*setp = lv_label_create(c);
|
||||
lv_label_set_text(*setp, "Soll --.-");
|
||||
lv_obj_set_style_text_color(*setp, COL_TEXT_DIM, 0);
|
||||
lv_obj_set_style_text_font(*setp, font_text(), 0);
|
||||
lv_obj_set_width(*setp, LV_PCT(100));
|
||||
lv_obj_set_style_text_align(*setp, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_height(*setp, lv_font_get_line_height(font_text()) + 6);
|
||||
|
||||
*bar = lv_bar_create(c);
|
||||
lv_obj_set_size(*bar, LV_PCT(92), 8);
|
||||
@@ -881,19 +1097,29 @@ static lv_obj_t* tempField(lv_obj_t* parent, const char* title, lv_color_t accen
|
||||
// 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.
|
||||
static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t** ser) {
|
||||
static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t** ser,
|
||||
float jetzt, float soll) {
|
||||
lv_obj_t* ch = lv_chart_create(parent);
|
||||
lv_obj_set_size(ch, LV_PCT(96), 72);
|
||||
lv_obj_set_size(ch, LV_PCT(96), 64);
|
||||
lv_chart_set_type(ch, LV_CHART_TYPE_LINE);
|
||||
lv_chart_set_point_count(ch, DASH_SPARK_POINTS);
|
||||
lv_chart_set_div_line_count(ch, 0, 0);
|
||||
// Schwach abgesetzte Flaeche: Ohne sie waere der Bereich vor dem ersten Messwert
|
||||
// vollstaendig unsichtbar - man haette gar nicht erkannt, dass dort eine Kurve gehoert.
|
||||
lv_obj_set_style_bg_opa(ch, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(ch, 0, 0);
|
||||
lv_obj_set_style_pad_all(ch, 0, 0);
|
||||
lv_obj_set_style_pad_all(ch, 4, 0);
|
||||
lv_obj_set_style_size(ch, 0, 0, LV_PART_INDICATOR); // keine Punktmarkierungen
|
||||
lv_obj_set_style_line_width(ch, 3, LV_PART_ITEMS);
|
||||
lv_obj_clear_flag(ch, LV_OBJ_FLAG_SCROLLABLE);
|
||||
*ser = lv_chart_add_series(ch, col, LV_CHART_AXIS_PRIMARY_Y);
|
||||
|
||||
// Ausschnitt und Startwert sofort setzen. Ein frisch angelegtes Diagramm hat lauter
|
||||
// "kein Wert"-Punkte und zeichnet deshalb nichts - beim Umschalten der Darstellung
|
||||
// saehe man bis zur ersten Sekundenprobe eine leere Flaeche.
|
||||
int32_t sollT = (int32_t)(soll * 10.0f);
|
||||
lv_chart_set_range(ch, LV_CHART_AXIS_PRIMARY_Y, sollT - 60, sollT + 30);
|
||||
lv_chart_set_all_value(ch, *ser, (int32_t)(jetzt * 10.0f));
|
||||
return ch;
|
||||
}
|
||||
|
||||
@@ -922,7 +1148,7 @@ static void build_temp_row_content() {
|
||||
// zwei Drittel Flaeche fuer Wasser, ein Drittel fuer Dampf - die Asymmetrie ist die
|
||||
// Aussage, nicht ein Versehen.
|
||||
tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||
2, &lv_font_maven_pro_140, false, 0);
|
||||
2, &lv_font_maven_pro_bold_140, false, 0);
|
||||
tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_48, false, 0);
|
||||
break;
|
||||
@@ -930,11 +1156,11 @@ static void build_temp_row_content() {
|
||||
|
||||
case TD_VERLAUF: {
|
||||
lv_obj_t* cw = tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||
1, &lv_font_maven_pro_96, false, 0);
|
||||
g_sparkW = tempSpark(cw, COL_OK, &g_sparkSerW);
|
||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||
g_sparkW = tempSpark(cw, COL_OK, &g_sparkSerW, g_state.tempW, g_state.setW);
|
||||
lv_obj_t* cd = tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_96, false, 0);
|
||||
g_sparkD = tempSpark(cd, COL_ACCENT, &g_sparkSerD);
|
||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||
g_sparkD = tempSpark(cd, COL_ACCENT, &g_sparkSerD, g_state.tempD, g_state.setD);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -942,9 +1168,9 @@ static void build_temp_row_content() {
|
||||
// Keine Kartenkanten: Der Bereich ist in zwei Felder geteilt, getrennt nur durch
|
||||
// einen Hauch Helligkeitsunterschied.
|
||||
tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||
1, &lv_font_maven_pro_140, true, 0x0d0d0d);
|
||||
1, &lv_font_maven_pro_bold_140, true, 0x0d0d0d);
|
||||
tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_140, true, 0x151515);
|
||||
1, &lv_font_maven_pro_bold_140, true, 0x151515);
|
||||
lv_obj_set_style_pad_column(g_tempRow, 0, 0); // Felder stossen direkt aneinander
|
||||
break;
|
||||
}
|
||||
@@ -975,9 +1201,9 @@ static void build_temp_row_content() {
|
||||
lv_obj_set_flex_flow(inner, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_clear_flag(inner, LV_OBJ_FLAG_SCROLLABLE);
|
||||
tempField(inner, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||
1, &lv_font_maven_pro_96, false, 0);
|
||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||
tempField(inner, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_96, false, 0);
|
||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -999,6 +1225,10 @@ static lv_obj_t* infoCell(lv_obj_t* parent, const char* title, lv_obj_t** val, l
|
||||
lv_obj_set_style_text_color(*val, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*val, font_title(), 0);
|
||||
lv_obj_set_style_text_align(*val, LV_TEXT_ALIGN_CENTER, 0); // fuer mehrzeilige Werte
|
||||
// Feste Masse in BEIDE Richtungen - siehe die ausfuehrliche Begruendung in tempField.
|
||||
// Zwei Zeilen Platz, weil manche Werte zweizeilig sind ("32,4 g / 36,0 g").
|
||||
lv_obj_set_width(*val, LV_PCT(100));
|
||||
lv_obj_set_height(*val, lv_font_get_line_height(font_title()) * 2 + 6);
|
||||
return card;
|
||||
}
|
||||
// Tipp auf die Statuszeile: Wartung -> Serviceseite, Reinigung -> Sub-Screen,
|
||||
@@ -1075,6 +1305,7 @@ static void build_dashboard(lv_obj_t* p) {
|
||||
|
||||
// Info-Zeile: Timer / Bezug / Eco-Countdown / Gehaeuse (teilen sich die Breite)
|
||||
lv_obj_t* ir = lv_obj_create(p);
|
||||
g_infoRow = ir;
|
||||
lv_obj_set_width(ir, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(ir, 2);
|
||||
lv_obj_set_style_bg_opa(ir, LV_OPA_TRANSP, 0); lv_obj_set_style_border_width(ir, 0, 0);
|
||||
@@ -1169,7 +1400,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();
|
||||
@@ -1177,7 +1408,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();
|
||||
@@ -2845,6 +3076,25 @@ static void design_btn_cb(lv_event_t* e) {
|
||||
ui_update(g_state); // neu aufgebaute Anzeige sofort mit den aktuellen Werten fuellen
|
||||
}
|
||||
// Live-Bezugsschirm ein-/ausschalten (P4-lokal in NVS gespeichert)
|
||||
// Temperaturzahl einfaerben oder weiss lassen. Wirkt sofort - die Farbe wird bei jeder
|
||||
// Zustandsmeldung neu gesetzt, es muss nichts neu aufgebaut werden.
|
||||
// Groesse der Standby-Uhr waehlen. Wirkt sofort - es haengt an einem einzigen Textfeld.
|
||||
static void clock_size_cb(lv_event_t* e) {
|
||||
g_clockSize = (uint8_t)(intptr_t)lv_event_get_user_data(e);
|
||||
{ Preferences prefs; prefs.begin("ui", false); prefs.putUChar("uhrgr", g_clockSize); prefs.end(); }
|
||||
clock_size_apply();
|
||||
for (int i = 0; i < 3; i++) if (g_clockSizeBtn[i]) {
|
||||
bool an = (i == g_clockSize);
|
||||
lv_obj_set_style_bg_color(g_clockSizeBtn[i], an ? COL_ACCENT : COL_CARD2, 0);
|
||||
lv_obj_set_style_text_color(g_clockSizeBtn[i], an ? lv_color_hex(0x000000) : COL_TEXT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void temp_color_sw_cb(lv_event_t* e) {
|
||||
g_tempColored = sw_get((lv_obj_t*)lv_event_get_target(e));
|
||||
{ Preferences prefs; prefs.begin("ui", false); prefs.putBool("tempcol", g_tempColored); prefs.end(); }
|
||||
}
|
||||
|
||||
static void brew_live_sw_cb(lv_event_t* e) {
|
||||
g_brewLiveEnabled = sw_get((lv_obj_t*)lv_event_get_target(e));
|
||||
{ Preferences prefs; prefs.begin("ui", false); prefs.putBool("brewlive", g_brewLiveEnabled); prefs.end(); }
|
||||
@@ -2882,6 +3132,24 @@ static void build_info(lv_obj_t* p) {
|
||||
lv_obj_add_event_cb(fb, font_pick_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
|
||||
}
|
||||
|
||||
lv_obj_t* cl = lv_label_create(dcard);
|
||||
lv_label_set_text(cl, "Uhrgröße im Standby");
|
||||
lv_obj_set_style_text_color(cl, COL_TEXT_DIM, 0);
|
||||
lv_obj_t* crow = row(dcard, nullptr);
|
||||
lv_obj_set_style_pad_column(crow, 8, 0);
|
||||
static const char* UHR_NAMEN[3] = { "Klein", "Mittel", "Groß" };
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
lv_obj_t* b = (i == g_clockSize) ? th_accent_button(crow, UHR_NAMEN[i])
|
||||
: th_button(crow, UHR_NAMEN[i], COL_CARD2, COL_TEXT);
|
||||
lv_obj_set_flex_grow(b, 1);
|
||||
lv_obj_add_event_cb(b, clock_size_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
|
||||
g_clockSizeBtn[i] = b;
|
||||
}
|
||||
|
||||
lv_obj_t* swTempCol = add_switch(dcard, "Temperaturzahl in der Zustandsfarbe");
|
||||
sw_set(swTempCol, g_tempColored);
|
||||
lv_obj_add_event_cb(swTempCol, temp_color_sw_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
swBrewLive = add_switch(dcard, "Live-Bezugsschirm (Vollbild während des Bezugs)");
|
||||
sw_set(swBrewLive, g_brewLiveEnabled);
|
||||
lv_obj_add_event_cb(swBrewLive, brew_live_sw_cb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
@@ -2913,11 +3181,12 @@ static void build_info(lv_obj_t* p) {
|
||||
static void stats_request() {
|
||||
if (!g_client) return;
|
||||
if (g_statSummary) label_set_if_changed(g_statSummary, "Lade Statistik...");
|
||||
if (g_statList) label_set_if_changed(g_statList, "");
|
||||
stat_list_finish(0);
|
||||
g_client->sendGetUsageStats();
|
||||
}
|
||||
static void stats_refresh_cb(lv_event_t*) { stats_request(); }
|
||||
|
||||
|
||||
static void build_stats(lv_obj_t* p) {
|
||||
lv_obj_set_scroll_dir(p, LV_DIR_VER);
|
||||
lv_obj_set_scrollbar_mode(p, LV_SCROLLBAR_MODE_AUTO);
|
||||
@@ -2942,15 +3211,7 @@ static void build_stats(lv_obj_t* p) {
|
||||
|
||||
// Letzte Einzelbezuege
|
||||
section_title(p, "Letzte Bezüge");
|
||||
lv_obj_t* lc = th_card(p);
|
||||
lv_obj_set_width(lc, LV_PCT(100));
|
||||
lv_obj_set_height(lc, LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(lc, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_clear_flag(lc, LV_OBJ_FLAG_SCROLLABLE);
|
||||
g_statList = lv_label_create(lc);
|
||||
lv_obj_set_width(g_statList, LV_PCT(100));
|
||||
lv_label_set_text(g_statList, "");
|
||||
lv_obj_set_style_text_color(g_statList, COL_TEXT_DIM, 0);
|
||||
stat_list_build(p);
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
@@ -3002,10 +3263,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
|
||||
@@ -3214,6 +3500,73 @@ static void dash_timers_cb(lv_timer_t*) {
|
||||
// sonst greifen sie auf nicht angelegte LVGL-Objekte zu.
|
||||
static bool g_uiReady = false;
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Zeitmessung fuer langsame Bildaufbauten
|
||||
//
|
||||
// Beim 7-Zoll-Panel dauern manche Vorgaenge spuerbar lange, und aus dem Quelltext allein
|
||||
// ist nicht zu sehen, welcher Teil die Zeit frisst. LVGL meldet Anfang und Ende jedes
|
||||
// Zeichenzyklus - hier wird die Dauer gemessen und nur dann ausgegeben, wenn sie eine
|
||||
// Schwelle ueberschreitet. Im Normalbetrieb bleibt die Konsole still.
|
||||
// ------------------------------------------------------------------------------------
|
||||
#define REFR_WARN_MS 200
|
||||
|
||||
static uint32_t g_refrStart = 0;
|
||||
|
||||
static uint32_t g_refrPixels = 0;
|
||||
static uint32_t g_refrAreas = 0;
|
||||
static lv_area_t g_refrArea[6];
|
||||
static uint8_t g_refrAreaCnt = 0;
|
||||
|
||||
// Beim Start eines Zeichenzyklus festhalten, WIE VIEL neu gezeichnet wird. Erst damit laesst
|
||||
// sich unterscheiden, ob LVGL nur kleine Bereiche anfasst - dann ist das Zeichnen an sich zu
|
||||
// langsam - oder ob jedes Mal der ganze Bildschirm neu entsteht, weil ihn etwas unnoetig
|
||||
// ungueltig macht.
|
||||
static void refr_start_cb(lv_event_t*) {
|
||||
g_refrStart = lv_tick_get();
|
||||
g_refrPixels = 0;
|
||||
g_refrAreas = 0;
|
||||
g_refrAreaCnt = 0;
|
||||
lv_display_t* d = lv_display_get_default();
|
||||
if (!d) return;
|
||||
for (uint16_t i = 0; i < d->inv_p; i++) {
|
||||
if (d->inv_area_joined[i]) continue;
|
||||
const lv_area_t* a = &d->inv_areas[i];
|
||||
g_refrPixels += (uint32_t)(a->x2 - a->x1 + 1) * (uint32_t)(a->y2 - a->y1 + 1);
|
||||
if (g_refrAreaCnt < 6) g_refrArea[g_refrAreaCnt++] = *a;
|
||||
g_refrAreas++;
|
||||
}
|
||||
}
|
||||
|
||||
static void refr_ready_cb(lv_event_t*) {
|
||||
uint32_t d = lv_tick_get() - g_refrStart;
|
||||
if (d < REFR_WARN_MS) return;
|
||||
unsigned pct = (unsigned)((uint64_t)g_refrPixels * 100ULL / ((uint64_t)LCD_H_RES * LCD_V_RES));
|
||||
esp_rom_printf("[UI] Bildaufbau %u ms, %u Bereiche, %u %% der Flaeche\n",
|
||||
(unsigned)d, (unsigned)g_refrAreas, pct);
|
||||
// Lage der Bereiche mit ausgeben. Erst daran ist zu erkennen, WELCHER Teil der
|
||||
// Oberflaeche das Neuzeichnen ausloest - die Groesse allein sagt das nicht.
|
||||
for (uint8_t i = 0; i < g_refrAreaCnt; i++)
|
||||
esp_rom_printf(" Bereich %u: x %d..%d, y %d..%d\n", (unsigned)i,
|
||||
(int)g_refrArea[i].x1, (int)g_refrArea[i].x2,
|
||||
(int)g_refrArea[i].y1, (int)g_refrArea[i].y2);
|
||||
}
|
||||
|
||||
// Meldet, wenn ein ganzer Container ungueltig gemacht wird. Damit laesst sich unterscheiden,
|
||||
// ob eine einzelne Beschriftung den Bildaufbau ausloest oder ob gleich der komplette
|
||||
// Inhaltsbereich neu gezeichnet werden soll - zwei verschiedene Ursachen.
|
||||
static void inval_watch_cb(lv_event_t* e) {
|
||||
const char* name = (const char*)lv_event_get_user_data(e);
|
||||
esp_rom_printf("[UI] %s wird komplett ungueltig\n", name);
|
||||
}
|
||||
|
||||
static void refr_monitor_install() {
|
||||
lv_display_t* d = lv_display_get_default();
|
||||
if (!d) return;
|
||||
lv_display_add_event_cb(d, refr_start_cb, LV_EVENT_REFR_START, nullptr);
|
||||
lv_display_add_event_cb(d, refr_ready_cb, LV_EVENT_REFR_READY, nullptr);
|
||||
}
|
||||
|
||||
void ui_init(ProtocolClient* client) {
|
||||
g_client = client;
|
||||
|
||||
@@ -3231,6 +3584,9 @@ void ui_init(ProtocolClient* client) {
|
||||
g_tempDesign = prefs.getUChar("tempdsgn", TD_BAND);
|
||||
if (g_tempDesign >= TD_DESIGN_COUNT) g_tempDesign = TD_BAND;
|
||||
g_brewLiveEnabled = prefs.getBool("brewlive", true);
|
||||
g_tempColored = prefs.getBool("tempcol", false);
|
||||
g_clockSize = prefs.getUChar("uhrgr", UHR_GROSS);
|
||||
if (g_clockSize > UHR_GROSS) g_clockSize = UHR_GROSS;
|
||||
g_chartWin = prefs.getUChar("chartwin", 0);
|
||||
if (g_chartWin > 3) g_chartWin = 0;
|
||||
g_chartDuty = prefs.getBool("chartduty", false);
|
||||
@@ -3321,6 +3677,11 @@ void ui_init(ProtocolClient* client) {
|
||||
lv_obj_set_style_text_color(g_clock, COL_TEXT, 0);
|
||||
lv_obj_set_style_pad_right(g_clock, 10, 0);
|
||||
lv_obj_set_style_text_font(g_clock, font_text(), 0);
|
||||
// Feste Masse, sonst rechnet LVGL beim Wechsel der Minute das Layout der Kopfleiste neu
|
||||
// und macht sie samt Menue-Knopf ungueltig (siehe tempField).
|
||||
lv_obj_set_width(g_clock, 130);
|
||||
lv_obj_set_height(g_clock, lv_font_get_line_height(font_text()) + 4);
|
||||
lv_obj_set_style_text_align(g_clock, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
|
||||
g_wifiIcon = lv_label_create(header);
|
||||
lv_label_set_text(g_wifiIcon, LV_SYMBOL_WIFI);
|
||||
@@ -3354,6 +3715,9 @@ void ui_init(ProtocolClient* client) {
|
||||
g_drawer = lv_obj_create(scr);
|
||||
lv_obj_set_size(g_drawer, LV_PCT(100), DISP_VER_RES - 44);
|
||||
lv_obj_set_pos(g_drawer, 0, 44);
|
||||
// Bewusst durchscheinend: Der Vorhang legt sich ueber die ganze Seite, und der Inhalt
|
||||
// dahinter soll sichtbar bleiben. Ein Versuch mit deckendem Vorhang (gegen die
|
||||
// Zeichenlast) hat den Seiteninhalt verschwinden lassen und ist zurueckgenommen.
|
||||
lv_obj_set_style_bg_color(g_drawer, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_bg_opa(g_drawer, LV_OPA_50, 0);
|
||||
lv_obj_set_style_border_width(g_drawer, 0, 0);
|
||||
@@ -3422,7 +3786,7 @@ void ui_init(ProtocolClient* client) {
|
||||
g_standbyClock = lv_label_create(g_standbyScreen);
|
||||
lv_label_set_text(g_standbyClock, "--:--");
|
||||
lv_obj_set_style_text_color(g_standbyClock, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(g_standbyClock, &lv_font_clock_240, 0); // grosse Uhr im Standby
|
||||
lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0); // Groesse einstellbar
|
||||
lv_obj_center(g_standbyClock);
|
||||
lv_obj_add_event_cb(g_standbyScreen, standby_screen_cb, LV_EVENT_CLICKED, nullptr); // Tippen -> Aufweck-Abfrage
|
||||
lv_obj_t* standbyHint = lv_label_create(g_standbyScreen); // dezenter Hinweis am unteren Rand
|
||||
@@ -3435,13 +3799,25 @@ void ui_init(ProtocolClient* client) {
|
||||
g_wakeConfirm = lv_obj_create(lv_layer_top());
|
||||
lv_obj_set_size(g_wakeConfirm, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_bg_color(g_wakeConfirm, lv_color_hex(0x000000), 0);
|
||||
#if JC_PANEL_TYPE == WS_PANEL_7H
|
||||
// Deckend statt halbtransparent. Ein durchscheinender Vorhang ueber den ganzen
|
||||
// Bildschirm zwingt LVGL, einen Vollbild-Zwischenpuffer anzulegen und die gesamte
|
||||
// Flaeche zu mischen - bei 1280x720 in 24 Bit dauerte das rund fuenf Sekunden, in
|
||||
// denen nach dem Fingertipp nur der schwarze Standby-Bildschirm zu sehen war.
|
||||
// Verloren geht dabei nichts: Dahinter liegt im Standby ohnehin nur Schwarz.
|
||||
lv_obj_set_style_bg_opa(g_wakeConfirm, LV_OPA_COVER, 0);
|
||||
#else
|
||||
lv_obj_set_style_bg_opa(g_wakeConfirm, LV_OPA_70, 0);
|
||||
#endif
|
||||
lv_obj_set_style_border_width(g_wakeConfirm, 0, 0);
|
||||
lv_obj_set_style_radius(g_wakeConfirm, 0, 0);
|
||||
lv_obj_clear_flag(g_wakeConfirm, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_CLICKABLE); // Scrim faengt Tipps ab
|
||||
lv_obj_t* wakeCard = th_card(g_wakeConfirm);
|
||||
lv_obj_set_width(wakeCard, 420);
|
||||
// Breite waechst mit der gewaehlten Schriftgroesse mit. Bei fester Breite brach der
|
||||
// Text in der Stufe Gross an unguenstigen Stellen um.
|
||||
lv_obj_set_width(wakeCard, (g_fontStep == FONT_GROSS) ? 900
|
||||
: (g_fontStep == FONT_MITTEL) ? 640 : 420);
|
||||
lv_obj_set_height(wakeCard, LV_SIZE_CONTENT);
|
||||
lv_obj_center(wakeCard);
|
||||
lv_obj_set_flex_flow(wakeCard, LV_FLEX_FLOW_COLUMN);
|
||||
@@ -3464,12 +3840,17 @@ void ui_init(ProtocolClient* client) {
|
||||
lv_obj_set_flex_flow(wakeBtns, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(wakeBtns, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(wakeBtns, LV_OBJ_FLAG_SCROLLABLE);
|
||||
// Knopfbreite und -schrift wachsen mit der Schriftgroesse mit; mit fester Breite und
|
||||
// geerbter Grundschrift wirkten die Knoepfe neben dem groesseren Text verloren.
|
||||
const int32_t wakeBtnW = (g_fontStep == FONT_GROSS) ? 280 : (g_fontStep == FONT_MITTEL) ? 210 : 150;
|
||||
lv_obj_t* wakeNo = th_button(wakeBtns, "Abbrechen", COL_CARD2, COL_TEXT);
|
||||
lv_obj_set_width(wakeNo, 150);
|
||||
lv_obj_set_width(wakeNo, wakeBtnW);
|
||||
lv_obj_set_style_text_font(wakeNo, font_text(), 0);
|
||||
lv_obj_add_event_cb(wakeNo, wake_no_cb, LV_EVENT_CLICKED, nullptr);
|
||||
g_wakeNoBtn = wakeNo;
|
||||
lv_obj_t* wakeYes = th_accent_button(wakeBtns, "Aufwecken");
|
||||
lv_obj_set_width(wakeYes, 150);
|
||||
lv_obj_set_width(wakeYes, wakeBtnW);
|
||||
lv_obj_set_style_text_font(wakeYes, font_text(), 0);
|
||||
lv_obj_add_event_cb(wakeYes, wake_yes_cb, LV_EVENT_CLICKED, nullptr);
|
||||
g_wakeYesLbl = lv_obj_get_child(wakeYes, 0); // Label des Knopfs (fuer "Espresso" im Abfrage-Modus)
|
||||
// Zweite Zeile: direkt in die kalte Extraktion aufwecken. Ohne diesen Weg muesste man
|
||||
@@ -3477,6 +3858,7 @@ void ui_init(ProtocolClient* client) {
|
||||
// Nur sichtbar, wenn die S3 die Funktion freigeschaltet meldet (siehe ui_update).
|
||||
g_wakeColdBtn = th_button(wakeCard, "Cold Extraction (Wasser bleibt kalt)", COL_CARD2, COL_ACCENT);
|
||||
lv_obj_set_width(g_wakeColdBtn, LV_PCT(100));
|
||||
lv_obj_set_style_text_font(g_wakeColdBtn, font_text(), 0);
|
||||
lv_obj_add_event_cb(g_wakeColdBtn, wake_cold_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_add_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -3587,6 +3969,9 @@ void ui_init(ProtocolClient* client) {
|
||||
show_page(PG_DASH);
|
||||
if (g_client) g_client->sendListProfiles();
|
||||
|
||||
refr_monitor_install();
|
||||
if (g_content) lv_obj_add_event_cb(g_content, inval_watch_cb, LV_EVENT_INVALIDATE_AREA, (void*)"Inhaltsbereich");
|
||||
if (g_pages[PG_DASH]) lv_obj_add_event_cb(g_pages[PG_DASH], inval_watch_cb, LV_EVENT_INVALIDATE_AREA, (void*)"Dashboard-Seite");
|
||||
g_uiReady = true;
|
||||
}
|
||||
|
||||
@@ -3653,10 +4038,90 @@ static void ready_band_update(const MachineState& st) {
|
||||
else if (wOk) { txt = "Wasser bereit — Dampf heizt"; bg = 0x3a2a05; fg = COL_ACCENT; }
|
||||
else { txt = "Heizt auf"; bg = 0x3a2a05; fg = COL_ACCENT; }
|
||||
}
|
||||
lv_label_set_text(g_readyBandLbl, txt);
|
||||
lv_obj_set_style_text_color(g_readyBandLbl, fg, 0);
|
||||
lv_obj_set_style_bg_color(g_readyBand, lv_color_hex(bg), 0);
|
||||
lv_obj_set_style_bg_opa(g_readyBand, LV_OPA_COVER, 0);
|
||||
// Das Band spannt sich ueber die volle Breite. Wuerde es bei jeder Zustandsmeldung neu
|
||||
// gesetzt, machte es allein schon einen Streifen ueber den ganzen Bildschirm ungueltig.
|
||||
label_set_if_changed(g_readyBandLbl, txt);
|
||||
label_color_if_changed(g_readyBandLbl, fg);
|
||||
if (lv_color_to_u32(lv_obj_get_style_bg_color(g_readyBand, LV_PART_MAIN)) != bg) {
|
||||
lv_obj_set_style_bg_color(g_readyBand, lv_color_hex(bg), 0);
|
||||
lv_obj_set_style_bg_opa(g_readyBand, LV_OPA_COVER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Bezugsanzeige: die untere Zeile wechselt ihren Zustand
|
||||
//
|
||||
// Im Ruhezustand ist der Temperaturbereich das Wichtigste - die haeufigste Frage lautet
|
||||
// "ist sie bereit?". Waehrend eines Bezugs kehrt sich das um: Dann zaehlen Zeit und
|
||||
// Gewicht, und die Kesseltemperaturen sind Nebensache. Statt zweier getrennter Ansichten
|
||||
// verschiebt die Seite nur das Gewicht zwischen ihren beiden Bereichen und vergroessert
|
||||
// die beiden Zahlen, auf die es dann ankommt.
|
||||
//
|
||||
// Der Wechsel haelt nach dem Bezug noch kurz an. Ohne dieses Nachhalten wuerde die Seite
|
||||
// bei jeder kurzen Unterbrechung hin- und herspringen - das waere unruhiger als gar kein
|
||||
// Wechsel.
|
||||
// ------------------------------------------------------------------------------------
|
||||
#define DASH_BREW_HOLD_MS 4000
|
||||
|
||||
static void dash_brew_mode(bool on) {
|
||||
if (on == g_dashBrewMode) return; // nur bei echtem Wechsel neu anordnen
|
||||
g_dashBrewMode = on;
|
||||
if (!g_tempRow || !g_infoRow) return;
|
||||
lv_obj_set_flex_grow(g_tempRow, on ? 1 : 3);
|
||||
lv_obj_set_flex_grow(g_infoRow, on ? 3 : 2);
|
||||
// Die Schrift von Shot-Timer und Waage bleibt unveraendert. Frueher wechselte sie
|
||||
// waehrend des Bezugs auf den grossen fetten Schnitt - das wirkte gegenueber dem
|
||||
// restlichen Dashboard zu laut. Der Bereich bekommt weiterhin mehr Platz, die Zahlen
|
||||
// stehen dadurch freier, aber in derselben Schrift wie sonst auch.
|
||||
}
|
||||
|
||||
static void dash_brew_mode_tick(const MachineState& st) {
|
||||
static bool war = false; // schon einmal ein Bezug gesehen?
|
||||
static uint32_t zuletzt = 0;
|
||||
bool aktiv = st.shotActive || st.steamCircuitActive;
|
||||
uint32_t jetzt = lv_tick_get();
|
||||
if (aktiv) { war = true; zuletzt = jetzt; }
|
||||
dash_brew_mode(aktiv || (war && (uint32_t)(jetzt - zuletzt) < DASH_BREW_HOLD_MS));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Uhr läuft lokal weiter
|
||||
//
|
||||
// Die Uhrzeit kommt von der Hauptplatine und ist leer, solange dort keine Zeitquelle
|
||||
// synchronisiert ist. Bei einem erneuten Abgleich ist sie kurz weg - auf dem Display
|
||||
// verschwand die Uhr dann fuer ein paar Sekunden, was gerade im Standby unruhig wirkt.
|
||||
//
|
||||
// Deshalb merkt sich der P4 die zuletzt empfangene Zeit und zaehlt zwischen den Meldungen
|
||||
// selbst weiter. Er stellt nichts richtig: Sobald die Hauptplatine wieder eine Zeit
|
||||
// schickt, gilt deren Wert. Die lokale Uhr ueberbrueckt nur die Luecke.
|
||||
// ------------------------------------------------------------------------------------
|
||||
static bool g_clockKnown = false; // schon einmal eine Zeit empfangen?
|
||||
static uint32_t g_clockMinute = 0; // zuletzt bekannte Uhrzeit in Minuten seit Mitternacht
|
||||
static uint32_t g_clockTick = 0; // Zeitpunkt (lv_tick), zu dem sie galt
|
||||
|
||||
// "HH:MM" der Hauptplatine uebernehmen. Andere Formate werden nicht angetastet.
|
||||
static void clock_note(const String& hhmm) {
|
||||
if (hhmm.length() < 5 || hhmm.charAt(2) != ':') return;
|
||||
int h = hhmm.substring(0, 2).toInt();
|
||||
int m = hhmm.substring(3, 5).toInt();
|
||||
if (h < 0 || h > 23 || m < 0 || m > 59) return;
|
||||
g_clockMinute = (uint32_t)(h * 60 + m);
|
||||
g_clockTick = lv_tick_get();
|
||||
g_clockKnown = true;
|
||||
}
|
||||
|
||||
// Anzuzeigende Uhrzeit: die der Hauptplatine, solange sie eine schickt - sonst die lokal
|
||||
// weitergezaehlte. Leerer Rueckgabewert heisst: noch nie eine Zeit gehabt, nichts anzeigen.
|
||||
static String clock_text(const String& vonS3) {
|
||||
if (vonS3.length()) return vonS3;
|
||||
if (!g_clockKnown) return String();
|
||||
uint32_t min = g_clockMinute + (uint32_t)((lv_tick_get() - g_clockTick) / 60000UL);
|
||||
char b[6];
|
||||
snprintf(b, sizeof(b), "%02u:%02u", (unsigned)((min / 60) % 24), (unsigned)(min % 60));
|
||||
return String(b);
|
||||
}
|
||||
|
||||
void ui_update(const MachineState& st) {
|
||||
@@ -3673,12 +4138,15 @@ void ui_update(const MachineState& st) {
|
||||
|
||||
// WLAN-Symbol
|
||||
bool wifiOk = st.wifiConnected && !st.apMode;
|
||||
if (g_wifiIcon) lv_obj_set_style_text_color(g_wifiIcon, wifiOk ? COL_OK : (st.apMode ? COL_WARN : COL_TEXT_MUT), 0);
|
||||
label_color_if_changed(g_wifiIcon, wifiOk ? COL_OK : (st.apMode ? COL_WARN : COL_TEXT_MUT));
|
||||
|
||||
// Uhrzeit (nur wenn NTP synchronisiert)
|
||||
// Uhrzeit. Angezeigt wird die der Hauptplatine; faellt sie voruebergehend aus, zaehlt
|
||||
// der P4 die zuletzt bekannte Zeit selbst weiter (siehe clock_text).
|
||||
clock_note(st.timeStr);
|
||||
String zeit = clock_text(st.timeStr);
|
||||
if (g_clock) {
|
||||
if (st.timeStr.length()) {
|
||||
lv_label_set_text(g_clock, st.timeStr.c_str());
|
||||
if (zeit.length()) {
|
||||
label_set_if_changed(g_clock, zeit.c_str());
|
||||
lv_obj_remove_flag(g_clock, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(g_clock, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -3691,13 +4159,16 @@ void ui_update(const MachineState& st) {
|
||||
if (g_standbyScreen) {
|
||||
bool shouldShow = st.standbyActive && st.standbyShowClock;
|
||||
if (shouldShow) {
|
||||
lv_label_set_text(g_standbyClock, st.timeStr.length() ? st.timeStr.c_str() : "--:--");
|
||||
label_set_if_changed(g_standbyClock, zeit.length() ? zeit.c_str() : "--:--");
|
||||
if (!g_standbyShown) {
|
||||
ui_fade_in(g_standbyScreen, UI_FADE_MS); // Einschlaf-Animation
|
||||
g_standbyShown = true;
|
||||
} else {
|
||||
lv_obj_move_foreground(g_standbyScreen);
|
||||
}
|
||||
// Frueher wurde die Uhr hier bei JEDER Zustandsmeldung nach vorn geholt. Das
|
||||
// ordnet die Ebenen neu und laesst LVGL den gesamten Bildschirm neu zeichnen -
|
||||
// einmal pro Sekunde, jedesmal 1280x720 in 24 Bit. Genau das war der Grund,
|
||||
// warum sich rund um den Standby alles um ein bis zwei Sekunden verzoegerte.
|
||||
// Nach vorn geholt wird die Uhr beim Einblenden, danach bleibt sie dort.
|
||||
} else if (g_standbyShown) {
|
||||
ui_fade_out_hide(g_standbyScreen, UI_FADE_MS); // Aufweck-Animation
|
||||
g_standbyShown = false;
|
||||
@@ -3958,47 +4429,48 @@ 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, 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, 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, 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);
|
||||
}
|
||||
ready_band_update(st);
|
||||
dash_brew_mode_tick(st);
|
||||
if (false) {
|
||||
}
|
||||
}
|
||||
@@ -4142,12 +4614,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) {
|
||||
@@ -4377,13 +4849,13 @@ void ui_set_usage_stats(const String& json) {
|
||||
JsonDocument doc;
|
||||
if (deserializeJson(doc, json)) {
|
||||
label_set_if_changed(g_statSummary, "Statistik konnte nicht gelesen werden.");
|
||||
if (g_statList) label_set_if_changed(g_statList, "");
|
||||
stat_list_finish(0);
|
||||
return;
|
||||
}
|
||||
bool avail = doc["available"] | false;
|
||||
if (!avail) {
|
||||
label_set_if_changed(g_statSummary, "Noch keine Nutzungsdaten vorhanden.");
|
||||
if (g_statList) label_set_if_changed(g_statList, "");
|
||||
stat_list_finish(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4392,8 +4864,7 @@ void ui_set_usage_stats(const String& json) {
|
||||
long week = doc["week"] | 0;
|
||||
bool partial = doc["partial"] | false;
|
||||
|
||||
// Liste + Mittelwert der angezeigten Bezuege aufbauen
|
||||
String l;
|
||||
// Mittelwert der angezeigten Bezuege; die Liste fuellt stat_list_set()
|
||||
int n = 0;
|
||||
float durSum = 0.0f;
|
||||
int nDur = 0; // Anzahl der Bezuege, die in den Mittelwert eingehen (ohne kalte)
|
||||
@@ -4402,10 +4873,7 @@ void ui_set_usage_stats(const String& json) {
|
||||
float d = v["d"] | 0.0f;
|
||||
float w = v["w"] | -1.0f;
|
||||
bool cold = v["cx"] | false; // kalte Extraktion (fehlt bei S3-FW < 5.3.0)
|
||||
if (n > 0) l += "\n";
|
||||
l += String(dt) + " " + String(d, 1) + " s";
|
||||
if (w >= 0.0f) l += " " + String(w, 1) + " g";
|
||||
if (cold) l += " (kalt)";
|
||||
stat_list_set(n, dt, d, w, cold);
|
||||
// Minutenlange kalte Bezuege wuerden den Mittelwert unbrauchbar machen -> raus
|
||||
// (gleiche Regel wie in der S3-Statistik)
|
||||
if (!cold) { durSum += d; nDur++; }
|
||||
@@ -4423,10 +4891,7 @@ void ui_set_usage_stats(const String& json) {
|
||||
}
|
||||
label_set_if_changed(g_statSummary, s.c_str());
|
||||
|
||||
if (g_statList) {
|
||||
if (n == 0) l = "Keine Einzelbezüge vorhanden.";
|
||||
label_set_if_changed(g_statList, l.c_str());
|
||||
}
|
||||
stat_list_finish(n);
|
||||
}
|
||||
|
||||
void ui_set_wifi_networks(const String& json) {
|
||||
|
||||
Reference in New Issue
Block a user