Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf57e11ccb | ||
|
|
6d345d2145 | ||
|
|
a410da6e72 | ||
|
|
f0a214d712 | ||
|
|
21f273aae4 | ||
|
|
e42d2b3de2 | ||
|
|
785f64331d | ||
|
|
40aabe48bc | ||
|
|
ca3a9c1264 | ||
|
|
bcd9ecf0c8 |
@@ -1,3 +1,80 @@
|
||||
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
|
||||
Fläche des 7-Zöllers leisten soll:
|
||||
* Gewichtet - Wasser bekommt zwei Drittel der Fläche, Dampf ein Drittel. Beim Espresso
|
||||
zählt die Wassertemperatur; die Asymmetrie ist die Aussage.
|
||||
* Verlauf - unter jeder Temperatur eine flache Kurve der letzten Minute. Die eigentliche
|
||||
Frage an eine PID-Regelung ist nicht „wie warm", sondern „hält sie" - und das kann eine
|
||||
Zahl grundsätzlich nicht zeigen. Der Ausschnitt hängt am Sollwert, damit die Abweichung
|
||||
sichtbar wird statt in der Skala unterzugehen.
|
||||
* Flächen - keine Kartenkanten, zwei Felder mit einem Hauch Helligkeitsunterschied.
|
||||
* Band - ein farbiges Band über die volle Breite sagt den Zustand, bevor eine Zahl
|
||||
gelesen wird: bereit, heizt, Bezug, Cold Extraction oder Standby. Neue Vorgabe.
|
||||
- Dafür gibt es zwei neue Schriftgrade (96 und 140 Pixel). Sie enthalten nur Ziffern,
|
||||
Komma, Punkt, Minus und Gradzeichen - mit vollem Zeichensatz wären sie um ein Vielfaches
|
||||
größer, ohne etwas beizutragen. Die größte Textschrift hatte bisher 48 Pixel, was für die
|
||||
großen Zahlen nicht reichte.
|
||||
- Ältere Stände haben in den Einstellungen noch die Nummer einer der früheren Darstellungen
|
||||
gespeichert; liegt sie außerhalb, fällt sie auf die Vorgabe zurück.
|
||||
- Der Aufbau der Bezugsanzeige - untere Zeile wechselt während eines Bezugs auf große
|
||||
Timer- und Gewichtsanzeige - folgt als nächster Schritt.
|
||||
|
||||
Version 1.7.3:
|
||||
- Die Schriftgröße steht jetzt auf der Info-Seite im Abschnitt „Darstellung", zusammen mit
|
||||
der Wahl der Temperaturanzeige und dem Live-Bezugsschirm. Dort gehört sie hin: Es ist
|
||||
|
||||
@@ -567,6 +567,8 @@
|
||||
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_bold_96) \
|
||||
LV_FONT_DECLARE(lv_font_maven_pro_bold_140) \
|
||||
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
+430
-326
@@ -22,6 +22,7 @@
|
||||
#include "ui.h"
|
||||
#include <Preferences.h>
|
||||
#include "esp_system.h"
|
||||
#include "display_hal.h"
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Schriftgroesse
|
||||
@@ -82,6 +83,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();
|
||||
}
|
||||
@@ -166,8 +171,16 @@ static void label_set_if_changed(lv_obj_t* lbl, const char* txt); // Definitio
|
||||
// TD_MINIMAL - rahmenlos, nur grosse Werte mit duenner Ist/Soll-Linie
|
||||
// TD_NEO - Soft-3D/Neumorph: Flaeche wirkt aus dem Hintergrund herausgedrueckt
|
||||
// TD_THERMO - vertikale Thermometer-Saeulen mit Kolben und Glanzstreifen
|
||||
enum TempDesign { TD_CARDS = 0, TD_GAUGE, TD_MINIMAL, TD_NEO, TD_THERMO, TD_DESIGN_COUNT };
|
||||
static uint8_t g_tempDesign = TD_GAUGE;
|
||||
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;
|
||||
static lv_chart_series_t* g_sparkSerW = nullptr;
|
||||
static lv_chart_series_t* g_sparkSerD = nullptr;
|
||||
static lv_obj_t* g_readyBand = nullptr; // Zustandsband der Darstellung TD_BAND
|
||||
static lv_obj_t* g_readyBandLbl = nullptr;
|
||||
static lv_obj_t* g_tempRow = nullptr; // Container; Inhalt wird je Design (neu) aufgebaut
|
||||
static lv_obj_t* lblTempW; static lv_obj_t* lblSetW; static lv_obj_t* barDutyW; // barDuty*: lv_bar (Karten/Minimal/Neo/Thermo-Saeule)
|
||||
static lv_obj_t* lblTempD; static lv_obj_t* lblSetD; static lv_obj_t* barDutyD;
|
||||
@@ -182,6 +195,10 @@ 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 const lv_font_t* g_shotFontNormal = nullptr;
|
||||
static const lv_font_t* g_bezugFontNormal = nullptr;
|
||||
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;
|
||||
@@ -348,7 +365,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
|
||||
@@ -567,6 +583,112 @@ 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -656,7 +778,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);
|
||||
@@ -671,6 +805,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);
|
||||
@@ -686,6 +826,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);
|
||||
@@ -798,7 +943,7 @@ static void temp_progress_set(lv_obj_t* bar, lv_obj_t* arc, lv_obj_t* bulb, int
|
||||
if (pct > 100) pct = 100; else if (pct < 0) pct = 0;
|
||||
if (bar) {
|
||||
lv_bar_set_value(bar, pct, LV_ANIM_OFF);
|
||||
if (g_tempDesign != TD_CARDS) lv_obj_set_style_bg_color(bar, col, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_bg_color(bar, col, LV_PART_INDICATOR);
|
||||
}
|
||||
if (arc) {
|
||||
lv_arc_set_value(arc, pct);
|
||||
@@ -806,298 +951,119 @@ 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);
|
||||
}
|
||||
static void tempCard(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** bar) {
|
||||
lv_obj_t* card = th_card(parent);
|
||||
lv_obj_set_size(card, LV_PCT(49), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(card, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(card, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(card, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_t* t = lv_label_create(card);
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_color(t, accent, 0);
|
||||
lv_obj_set_style_text_font(t, font_text(), 0);
|
||||
*big = lv_label_create(card);
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, &lv_font_maven_pro_48, 0);
|
||||
*setp = lv_label_create(card);
|
||||
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);
|
||||
*bar = lv_bar_create(card);
|
||||
lv_obj_set_size(*bar, LV_PCT(92), 12);
|
||||
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x3a3a3a), 0); // sichtbarer Track (auch bei 0 %)
|
||||
lv_obj_set_style_bg_opa(*bar, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_color(*bar, COL_BORDER, 0);
|
||||
lv_obj_set_style_border_width(*bar, 1, 0);
|
||||
lv_obj_set_style_radius(*bar, 6, 0);
|
||||
lv_obj_set_style_bg_color(*bar, accent, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_radius(*bar, 6, LV_PART_INDICATOR);
|
||||
lv_bar_set_range(*bar, 0, 100);
|
||||
lv_bar_set_value(*bar, 0, LV_ANIM_OFF);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Rundinstrument im Manometer-Look (Design TD_GAUGE): freistehend ohne Kartenrahmen.
|
||||
// Leichte 3D-Wirkung ueber einen "Metall"-Bezel (vertikaler Verlauf hell -> dunkel) mit
|
||||
// weichem Schlagschatten und ein leicht aufgehelltes Zifferblatt darunter. Der 270-Grad-
|
||||
// Bogen zeigt Ist/Soll in % und faerbt sich mit der Zustandsfarbe des Ist-Werts.
|
||||
static void tempGauge(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** arc) {
|
||||
lv_obj_t* holder = lv_obj_create(parent);
|
||||
lv_obj_set_size(holder, LV_PCT(49), LV_PCT(100));
|
||||
lv_obj_set_style_bg_opa(holder, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(holder, 0, 0);
|
||||
lv_obj_set_style_pad_all(holder, 0, 0);
|
||||
lv_obj_clear_flag(holder, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(holder, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(holder, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
// Bezel-Ring: Verlauf von hell (oben) nach dunkel (unten) + Schatten = Metall-Optik
|
||||
lv_obj_t* bezel = lv_obj_create(holder);
|
||||
lv_obj_set_size(bezel, 188, 188);
|
||||
lv_obj_center(bezel);
|
||||
lv_obj_set_style_radius(bezel, LV_RADIUS_CIRCLE, 0);
|
||||
lv_obj_set_style_bg_color(bezel, lv_color_hex(0x5c5c5c), 0);
|
||||
lv_obj_set_style_bg_grad_color(bezel, lv_color_hex(0x141414), 0);
|
||||
lv_obj_set_style_bg_grad_dir(bezel, LV_GRAD_DIR_VER, 0);
|
||||
lv_obj_set_style_border_width(bezel, 0, 0);
|
||||
lv_obj_set_style_pad_all(bezel, 0, 0);
|
||||
lv_obj_set_style_shadow_width(bezel, 16, 0);
|
||||
lv_obj_set_style_shadow_offset_y(bezel, 5, 0);
|
||||
lv_obj_set_style_shadow_color(bezel, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_shadow_opa(bezel, LV_OPA_60, 0);
|
||||
lv_obj_clear_flag(bezel, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(bezel, LV_OBJ_FLAG_CLICKABLE); // Tipp faellt zum Holder durch
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Temperaturbereich des Dashboards - vier Darstellungen
|
||||
//
|
||||
// Gemeinsam ist allen: zwei Kessel, die Zahl ist das Erste, was man sieht. Unterschiedlich
|
||||
// ist, was die Flaeche zusaetzlich leistet.
|
||||
//
|
||||
// Die grossen Zahlen brauchen Schriftgrade, die es vorher nicht gab (96 und 140 Pixel).
|
||||
// Diese beiden Schriften enthalten nur Ziffern, Komma, Punkt, Minus und Gradzeichen - mit
|
||||
// vollem Zeichensatz waeren sie um ein Vielfaches groesser, ohne etwas beizutragen.
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
// Zifferblatt: leicht "vertieft" (Verlauf dunkel oben -> etwas heller unten)
|
||||
lv_obj_t* face = lv_obj_create(bezel);
|
||||
lv_obj_set_size(face, 174, 174);
|
||||
lv_obj_center(face);
|
||||
lv_obj_set_style_radius(face, LV_RADIUS_CIRCLE, 0);
|
||||
lv_obj_set_style_bg_color(face, lv_color_hex(0x0c0c0c), 0);
|
||||
lv_obj_set_style_bg_grad_color(face, lv_color_hex(0x232323), 0);
|
||||
lv_obj_set_style_bg_grad_dir(face, LV_GRAD_DIR_VER, 0);
|
||||
lv_obj_set_style_border_width(face, 0, 0);
|
||||
lv_obj_set_style_pad_all(face, 0, 0);
|
||||
lv_obj_clear_flag(face, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(face, LV_OBJ_FLAG_CLICKABLE);
|
||||
// Ein Kesselfeld. 'grow' verteilt die Breite zwischen den beiden, 'valueFont' bestimmt die
|
||||
// Groesse der Zahl, 'flat' laesst den Kartenrahmen weg und faerbt stattdessen die Flaeche.
|
||||
static lv_obj_t* tempField(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** bar,
|
||||
uint8_t grow, const lv_font_t* valueFont,
|
||||
bool flat, uint32_t flatBg) {
|
||||
lv_obj_t* c;
|
||||
if (flat) {
|
||||
c = lv_obj_create(parent);
|
||||
lv_obj_set_style_bg_color(c, lv_color_hex(flatBg), 0);
|
||||
lv_obj_set_style_bg_opa(c, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(c, 0, 0);
|
||||
lv_obj_set_style_radius(c, 0, 0);
|
||||
lv_obj_set_style_pad_all(c, 14, 0);
|
||||
} else {
|
||||
c = th_card(parent);
|
||||
lv_obj_set_style_pad_all(c, 12, 0);
|
||||
}
|
||||
lv_obj_set_height(c, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(c, grow);
|
||||
lv_obj_set_flex_flow(c, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(c, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(c, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(c, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
// 270-Grad-Bogen (Luecke unten): reine Anzeige, kein Griff, nicht bedienbar
|
||||
*arc = lv_arc_create(face);
|
||||
lv_obj_set_size(*arc, 158, 158);
|
||||
lv_obj_center(*arc);
|
||||
lv_arc_set_rotation(*arc, 135);
|
||||
lv_arc_set_bg_angles(*arc, 0, 270);
|
||||
lv_arc_set_range(*arc, 0, 100);
|
||||
lv_arc_set_value(*arc, 0);
|
||||
lv_obj_remove_style(*arc, nullptr, LV_PART_KNOB);
|
||||
lv_obj_clear_flag(*arc, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_arc_width(*arc, 12, LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_color(*arc, lv_color_hex(0x2e2e2e), LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_width(*arc, 12, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_arc_color(*arc, accent, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_arc_rounded(*arc, true, LV_PART_INDICATOR);
|
||||
|
||||
lv_obj_t* t = lv_label_create(face);
|
||||
lv_obj_t* t = lv_label_create(c);
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_color(t, accent, 0);
|
||||
lv_obj_set_style_text_font(t, font_small(), 0);
|
||||
lv_obj_align(t, LV_ALIGN_CENTER, 0, -48);
|
||||
*big = lv_label_create(face);
|
||||
|
||||
*big = lv_label_create(c);
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, &lv_font_maven_pro_40, 0);
|
||||
lv_obj_align(*big, LV_ALIGN_CENTER, 0, -2);
|
||||
// Soll-Wert sitzt in der Bogen-Luecke unten (dort ist die volle Breite frei)
|
||||
*setp = lv_label_create(face);
|
||||
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_small(), 0);
|
||||
lv_obj_align(*setp, LV_ALIGN_CENTER, 0, 62);
|
||||
}
|
||||
// Minimal-Design (TD_MINIMAL): komplett rahmenlos - nur grosser Ist-Wert in der
|
||||
// Zustandsfarbe, darunter eine duenne Ist/Soll-Linie und der Soll-Wert. Sehr ruhig.
|
||||
static void tempMinimal(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** bar) {
|
||||
lv_obj_t* holder = lv_obj_create(parent);
|
||||
lv_obj_set_size(holder, LV_PCT(49), LV_PCT(100));
|
||||
lv_obj_set_style_bg_opa(holder, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(holder, 0, 0);
|
||||
lv_obj_set_style_pad_all(holder, 0, 0);
|
||||
lv_obj_set_style_pad_row(holder, 10, 0);
|
||||
lv_obj_set_flex_flow(holder, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(holder, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(holder, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(holder, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(holder, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_t* t = lv_label_create(holder);
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_color(t, accent, 0);
|
||||
lv_obj_set_style_text_font(t, font_text(), 0);
|
||||
*big = lv_label_create(holder);
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, &lv_font_maven_pro_48, 0);
|
||||
*bar = lv_bar_create(holder); // duenne Ist/Soll-Linie
|
||||
lv_obj_set_size(*bar, LV_PCT(64), 4);
|
||||
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x2a2a2a), 0);
|
||||
lv_obj_set_style_bg_opa(*bar, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(*bar, 0, 0);
|
||||
lv_obj_set_style_radius(*bar, 2, 0);
|
||||
lv_obj_set_style_bg_color(*bar, accent, LV_PART_INDICATOR); // folgt spaeter der Zustandsfarbe
|
||||
lv_obj_set_style_radius(*bar, 2, LV_PART_INDICATOR);
|
||||
lv_bar_set_range(*bar, 0, 100);
|
||||
lv_bar_set_value(*bar, 0, LV_ANIM_OFF);
|
||||
*setp = lv_label_create(holder);
|
||||
lv_obj_set_style_text_font(*big, valueFont, 0);
|
||||
// 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);
|
||||
}
|
||||
// Soft-3D/Neumorph (TD_NEO): Flaeche fast im Hintergrundton, wirkt herausgedrueckt.
|
||||
// LVGL kann nur einen Schatten je Objekt, daher zwei verschachtelte Objekte:
|
||||
// aussen dunkler Schatten unten rechts, innen heller "Lichtschein" oben links.
|
||||
static void tempNeo(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** bar) {
|
||||
lv_obj_t* shell = lv_obj_create(parent);
|
||||
lv_obj_set_size(shell, LV_PCT(48), LV_PCT(100));
|
||||
lv_obj_set_style_radius(shell, 20, 0);
|
||||
lv_obj_set_style_bg_color(shell, lv_color_hex(0x171717), 0);
|
||||
lv_obj_set_style_bg_opa(shell, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(shell, 0, 0);
|
||||
lv_obj_set_style_pad_all(shell, 0, 0);
|
||||
lv_obj_set_style_shadow_width(shell, 14, 0);
|
||||
lv_obj_set_style_shadow_offset_x(shell, 6, 0);
|
||||
lv_obj_set_style_shadow_offset_y(shell, 6, 0);
|
||||
lv_obj_set_style_shadow_color(shell, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_shadow_opa(shell, LV_OPA_70, 0);
|
||||
lv_obj_clear_flag(shell, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(shell, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(shell, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
lv_obj_t* inner = lv_obj_create(shell);
|
||||
lv_obj_set_size(inner, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_radius(inner, 20, 0);
|
||||
lv_obj_set_style_bg_color(inner, lv_color_hex(0x1e1e1e), 0); // leicht gewoelbt: oben etwas heller
|
||||
lv_obj_set_style_bg_grad_color(inner, lv_color_hex(0x121212), 0);
|
||||
lv_obj_set_style_bg_grad_dir(inner, LV_GRAD_DIR_VER, 0);
|
||||
lv_obj_set_style_border_width(inner, 0, 0);
|
||||
lv_obj_set_style_shadow_width(inner, 14, 0);
|
||||
lv_obj_set_style_shadow_offset_x(inner, -6, 0);
|
||||
lv_obj_set_style_shadow_offset_y(inner, -6, 0);
|
||||
lv_obj_set_style_shadow_color(inner, lv_color_hex(0x3c3c3c), 0);
|
||||
lv_obj_set_style_shadow_opa(inner, LV_OPA_50, 0);
|
||||
lv_obj_set_style_pad_row(inner, 8, 0);
|
||||
lv_obj_set_flex_flow(inner, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(inner, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(inner, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(inner, LV_OBJ_FLAG_CLICKABLE); // Tipp faellt zur Shell durch
|
||||
|
||||
lv_obj_t* t = lv_label_create(inner);
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_color(t, accent, 0);
|
||||
lv_obj_set_style_text_font(t, font_text(), 0);
|
||||
*big = lv_label_create(inner);
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, &lv_font_maven_pro_48, 0);
|
||||
*setp = lv_label_create(inner);
|
||||
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);
|
||||
*bar = lv_bar_create(inner); // "eingelassener" Balken: Track dunkler als Flaeche
|
||||
lv_obj_set_size(*bar, LV_PCT(78), 10);
|
||||
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x0a0a0a), 0);
|
||||
*bar = lv_bar_create(c);
|
||||
lv_obj_set_size(*bar, LV_PCT(92), 8);
|
||||
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x2c2c2c), 0);
|
||||
lv_obj_set_style_bg_opa(*bar, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(*bar, 0, 0);
|
||||
lv_obj_set_style_radius(*bar, 5, 0);
|
||||
lv_obj_set_style_bg_color(*bar, accent, LV_PART_INDICATOR); // folgt spaeter der Zustandsfarbe
|
||||
lv_obj_set_style_radius(*bar, 5, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_radius(*bar, 4, 0);
|
||||
lv_obj_set_style_bg_color(*bar, accent, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_radius(*bar, 4, LV_PART_INDICATOR);
|
||||
lv_bar_set_range(*bar, 0, 100);
|
||||
lv_bar_set_value(*bar, 0, LV_ANIM_OFF);
|
||||
return c;
|
||||
}
|
||||
// Thermometer-Saeulen (TD_THERMO): vertikale Saeule (lv_bar waechst bei Hochformat von
|
||||
// unten), Glanzstreifen als Glas-Effekt, runder Kolben unten - alles rahmenlos.
|
||||
static void tempThermo(lv_obj_t* parent, const char* title, lv_color_t accent,
|
||||
lv_obj_t** big, lv_obj_t** setp, lv_obj_t** bar, lv_obj_t** bulb) {
|
||||
lv_obj_t* holder = lv_obj_create(parent);
|
||||
lv_obj_set_size(holder, LV_PCT(49), LV_PCT(100));
|
||||
lv_obj_set_style_bg_opa(holder, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(holder, 0, 0);
|
||||
lv_obj_set_style_pad_all(holder, 0, 0);
|
||||
lv_obj_set_style_pad_column(holder, 14, 0);
|
||||
lv_obj_set_flex_flow(holder, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(holder, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(holder, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(holder, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Temperatur-Seite
|
||||
lv_obj_add_event_cb(holder, temp_card_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
lv_obj_t* th = lv_obj_create(holder); // Saeule + Kolben (feste Groesse)
|
||||
lv_obj_set_size(th, 48, 190);
|
||||
lv_obj_set_style_bg_opa(th, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(th, 0, 0);
|
||||
lv_obj_set_style_pad_all(th, 0, 0);
|
||||
lv_obj_clear_flag(th, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(th, LV_OBJ_FLAG_CLICKABLE);
|
||||
*bar = lv_bar_create(th); // hochkant -> fuellt sich von unten
|
||||
lv_obj_set_size(*bar, 20, 158);
|
||||
lv_obj_align(*bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x262626), 0);
|
||||
lv_obj_set_style_bg_opa(*bar, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(*bar, 0, 0);
|
||||
lv_obj_set_style_radius(*bar, 10, 0);
|
||||
lv_obj_set_style_bg_color(*bar, accent, LV_PART_INDICATOR); // folgt spaeter der Zustandsfarbe
|
||||
lv_obj_set_style_radius(*bar, 10, LV_PART_INDICATOR);
|
||||
lv_bar_set_range(*bar, 0, 100);
|
||||
lv_bar_set_value(*bar, 0, LV_ANIM_OFF);
|
||||
lv_obj_t* gloss = lv_obj_create(th); // Glanzstreifen (Glas-Effekt)
|
||||
lv_obj_set_size(gloss, 4, 138);
|
||||
lv_obj_align(gloss, LV_ALIGN_TOP_MID, -5, 8);
|
||||
lv_obj_set_style_bg_color(gloss, lv_color_hex(0xffffff), 0);
|
||||
lv_obj_set_style_bg_opa(gloss, LV_OPA_20, 0);
|
||||
lv_obj_set_style_border_width(gloss, 0, 0);
|
||||
lv_obj_set_style_radius(gloss, 2, 0);
|
||||
lv_obj_clear_flag(gloss, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(gloss, LV_OBJ_FLAG_CLICKABLE);
|
||||
*bulb = lv_obj_create(th); // Kolben (ueberlappt die Saeule leicht)
|
||||
lv_obj_set_size(*bulb, 36, 36);
|
||||
lv_obj_align(*bulb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_radius(*bulb, LV_RADIUS_CIRCLE, 0);
|
||||
lv_obj_set_style_bg_color(*bulb, accent, 0); // folgt spaeter der Zustandsfarbe
|
||||
lv_obj_set_style_bg_opa(*bulb, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(*bulb, 0, 0);
|
||||
lv_obj_set_style_shadow_width(*bulb, 10, 0);
|
||||
lv_obj_set_style_shadow_offset_y(*bulb, 3, 0);
|
||||
lv_obj_set_style_shadow_color(*bulb, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_shadow_opa(*bulb, LV_OPA_40, 0);
|
||||
lv_obj_clear_flag(*bulb, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(*bulb, LV_OBJ_FLAG_CLICKABLE);
|
||||
// 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,
|
||||
float jetzt, float soll) {
|
||||
lv_obj_t* ch = lv_chart_create(parent);
|
||||
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_color(ch, lv_color_hex(0x151515), 0);
|
||||
lv_obj_set_style_bg_opa(ch, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(ch, 6, 0);
|
||||
lv_obj_set_style_border_width(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);
|
||||
|
||||
lv_obj_t* col = lv_obj_create(holder); // Textspalte neben der Saeule
|
||||
lv_obj_set_size(col, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_bg_opa(col, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(col, 0, 0);
|
||||
lv_obj_set_style_pad_all(col, 0, 0);
|
||||
lv_obj_set_style_pad_row(col, 6, 0);
|
||||
lv_obj_set_flex_flow(col, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(col, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(col, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(col, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_t* t = lv_label_create(col);
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_color(t, accent, 0);
|
||||
lv_obj_set_style_text_font(t, font_text(), 0);
|
||||
*big = lv_label_create(col);
|
||||
lv_label_set_text(*big, "--.-");
|
||||
lv_obj_set_style_text_color(*big, COL_TEXT, 0);
|
||||
lv_obj_set_style_text_font(*big, &lv_font_maven_pro_48, 0);
|
||||
*setp = lv_label_create(col);
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
// Temperaturzeile gemaess aktivem Design (neu) befuellen - auch beim Umschalten zur Laufzeit
|
||||
|
||||
// Temperaturbereich gemaess aktiver Darstellung (neu) aufbauen - auch beim Umschalten
|
||||
static void build_temp_row_content() {
|
||||
if (!g_tempRow) return;
|
||||
lv_obj_clean(g_tempRow);
|
||||
@@ -1105,31 +1071,81 @@ static void build_temp_row_content() {
|
||||
lblTempD = lblSetD = barDutyD = nullptr;
|
||||
arcTempW = arcTempD = nullptr;
|
||||
bulbTempW = bulbTempD = nullptr;
|
||||
// Nur Soft-3D braucht Innenabstand, damit die weichen Schatten nicht am Rand abgeschnitten werden
|
||||
lv_obj_set_style_pad_hor(g_tempRow, (g_tempDesign == TD_NEO) ? 12 : 0, 0);
|
||||
lv_obj_set_style_pad_ver(g_tempRow, (g_tempDesign == TD_NEO) ? 12 : 0, 0);
|
||||
g_sparkW = g_sparkD = nullptr;
|
||||
g_sparkSerW = g_sparkSerD = nullptr;
|
||||
g_readyBand = g_readyBandLbl = nullptr;
|
||||
|
||||
const lv_color_t teal = lv_color_hex(0x80cbc4);
|
||||
lv_obj_set_style_pad_all(g_tempRow, 0, 0);
|
||||
lv_obj_set_style_pad_column(g_tempRow, 12, 0);
|
||||
lv_obj_set_style_pad_row(g_tempRow, 10, 0);
|
||||
lv_obj_set_flex_flow(g_tempRow, LV_FLEX_FLOW_ROW);
|
||||
|
||||
switch (g_tempDesign) {
|
||||
case TD_GAUGE:
|
||||
tempGauge(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &arcTempW);
|
||||
tempGauge(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &arcTempD);
|
||||
break;
|
||||
case TD_MINIMAL:
|
||||
tempMinimal(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW);
|
||||
tempMinimal(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD);
|
||||
break;
|
||||
case TD_NEO:
|
||||
tempNeo(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW);
|
||||
tempNeo(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD);
|
||||
break;
|
||||
case TD_THERMO:
|
||||
tempThermo(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW, &bulbTempW);
|
||||
tempThermo(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD, &bulbTempD);
|
||||
break;
|
||||
default:
|
||||
tempCard(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW);
|
||||
tempCard(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD);
|
||||
break;
|
||||
|
||||
case TD_GEWICHTET: {
|
||||
// Beim Espresso zaehlt die Wassertemperatur; der Dampfkessel ist Nebenwert. Also
|
||||
// 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_bold_140, false, 0);
|
||||
tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_48, false, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case TD_VERLAUF: {
|
||||
lv_obj_t* cw = tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||
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_bold_96, false, 0);
|
||||
g_sparkD = tempSpark(cd, COL_ACCENT, &g_sparkSerD, g_state.tempD, g_state.setD);
|
||||
break;
|
||||
}
|
||||
|
||||
case TD_FLAECHEN: {
|
||||
// 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_bold_140, true, 0x0d0d0d);
|
||||
tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_bold_140, true, 0x151515);
|
||||
lv_obj_set_style_pad_column(g_tempRow, 0, 0); // Felder stossen direkt aneinander
|
||||
break;
|
||||
}
|
||||
|
||||
default: { // TD_BAND
|
||||
// Ein farbiges Band ueber die volle Breite sagt den Zustand, bevor man eine Zahl
|
||||
// liest. Deshalb hier eine Spalte: Band oben, darunter die beiden Kessel.
|
||||
lv_obj_set_flex_flow(g_tempRow, LV_FLEX_FLOW_COLUMN);
|
||||
g_readyBand = lv_obj_create(g_tempRow);
|
||||
lv_obj_set_width(g_readyBand, LV_PCT(100));
|
||||
lv_obj_set_height(g_readyBand, 34);
|
||||
lv_obj_set_style_radius(g_readyBand, 8, 0);
|
||||
lv_obj_set_style_border_width(g_readyBand, 0, 0);
|
||||
lv_obj_set_style_pad_all(g_readyBand, 0, 0);
|
||||
lv_obj_clear_flag(g_readyBand, LV_OBJ_FLAG_SCROLLABLE);
|
||||
g_readyBandLbl = lv_label_create(g_readyBand);
|
||||
lv_obj_set_style_text_font(g_readyBandLbl, font_text(), 0);
|
||||
lv_label_set_text(g_readyBandLbl, "");
|
||||
lv_obj_center(g_readyBandLbl);
|
||||
|
||||
lv_obj_t* inner = lv_obj_create(g_tempRow);
|
||||
lv_obj_set_width(inner, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(inner, 1);
|
||||
lv_obj_set_style_bg_opa(inner, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(inner, 0, 0);
|
||||
lv_obj_set_style_pad_all(inner, 0, 0);
|
||||
lv_obj_set_style_pad_column(inner, 12, 0);
|
||||
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_bold_96, false, 0);
|
||||
tempField(inner, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
|
||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
static lv_obj_t* infoCell(lv_obj_t* parent, const char* title, lv_obj_t** val, lv_color_t accent, lv_obj_t** titleOut = nullptr) {
|
||||
@@ -1225,6 +1241,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);
|
||||
@@ -1260,6 +1277,10 @@ static void build_dashboard(lv_obj_t* p) {
|
||||
lv_bar_set_range(g_bbwBar, 0, 100);
|
||||
lv_bar_set_value(g_bbwBar, 0, LV_ANIM_OFF);
|
||||
lv_obj_add_flag(g_bbwBar, LV_OBJ_FLAG_HIDDEN);
|
||||
// Normalschriften merken, damit der Bezugsmodus sie wieder herstellen kann
|
||||
g_shotFontNormal = lv_obj_get_style_text_font(lblShotTimer, LV_PART_MAIN);
|
||||
g_bezugFontNormal = lv_obj_get_style_text_font(lblBezug, LV_PART_MAIN);
|
||||
|
||||
cellFlow = infoCell(ir, "FLOW", &lblFlow, lv_color_hex(0x64b5f6));
|
||||
lv_obj_add_flag(cellFlow, LV_OBJ_FLAG_CLICKABLE); // antippbar -> Brew-Control
|
||||
lv_obj_add_event_cb(cellFlow, brew_cell_cb, LV_EVENT_CLICKED, nullptr);
|
||||
@@ -1578,6 +1599,20 @@ static void chart_duty_btn_cb(lv_event_t*) {
|
||||
else lv_obj_add_flag(g_legDuty, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
chart_redraw();
|
||||
|
||||
// Verlaufskurven des Dashboards (Darstellung "Verlauf") mitfuehren. Der Ausschnitt
|
||||
// haengt am Sollwert, damit die Abweichung sichtbar wird und nicht in der Skala
|
||||
// untergeht - eine feste Skala von 0 bis 130 Grad zeigte nur eine gerade Linie.
|
||||
if (g_sparkSerW && g_sparkW) {
|
||||
int32_t soll = (int32_t)(g_state.setW * 10.0f);
|
||||
lv_chart_set_range(g_sparkW, LV_CHART_AXIS_PRIMARY_Y, soll - 60, soll + 30);
|
||||
lv_chart_set_next_value(g_sparkW, g_sparkSerW, (int32_t)(g_state.tempW * 10.0f));
|
||||
}
|
||||
if (g_sparkSerD && g_sparkD) {
|
||||
int32_t soll = (int32_t)(g_state.setD * 10.0f);
|
||||
lv_chart_set_range(g_sparkD, LV_CHART_AXIS_PRIMARY_Y, soll - 120, soll + 60);
|
||||
lv_chart_set_next_value(g_sparkD, g_sparkSerD, (int32_t)(g_state.tempD * 10.0f));
|
||||
}
|
||||
}
|
||||
static void build_chart(lv_obj_t* p) {
|
||||
lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE);
|
||||
@@ -2961,7 +2996,7 @@ static void info_block(lv_obj_t* card, const char* title, lv_obj_t** valOut, con
|
||||
if (valOut) *valOut = v;
|
||||
}
|
||||
// --- Darstellung: Dashboard-Temperaturdesign umschalten (P4-lokal, in NVS gespeichert) ---
|
||||
static const char* TEMP_DESIGN_NAMES[TD_DESIGN_COUNT] = { "Klassisch", "Instrumente", "Minimal", "Soft-3D", "Thermometer" };
|
||||
static const char* TEMP_DESIGN_NAMES[TD_DESIGN_COUNT] = { "Gewichtet", "Verlauf", "Flächen", "Band" };
|
||||
static void design_btns_refresh() {
|
||||
for (int i = 0; i < TD_DESIGN_COUNT; i++) {
|
||||
if (!g_designBtn[i]) continue;
|
||||
@@ -2981,6 +3016,13 @@ 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.
|
||||
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(); }
|
||||
@@ -3018,6 +3060,10 @@ 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* 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);
|
||||
@@ -3049,11 +3095,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);
|
||||
@@ -3078,15 +3125,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);
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
@@ -3362,13 +3401,17 @@ void ui_init(ProtocolClient* client) {
|
||||
}
|
||||
|
||||
{ Preferences prefs; prefs.begin("ui", false);
|
||||
g_tempDesign = prefs.getUChar("tempdsgn", TD_GAUGE);
|
||||
// Vorgabe ist jetzt TD_BAND. Aeltere Staende haben hier Werte der frueheren fuenf
|
||||
// Darstellungen stehen - liegt der Wert ausserhalb, faellt er auf die Vorgabe zurueck.
|
||||
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_chartWin = prefs.getUChar("chartwin", 0);
|
||||
if (g_chartWin > 3) g_chartWin = 0;
|
||||
g_chartDuty = prefs.getBool("chartduty", false);
|
||||
prefs.end();
|
||||
if (g_tempDesign >= TD_DESIGN_COUNT) g_tempDesign = TD_GAUGE; }
|
||||
}
|
||||
|
||||
font_step_load(); // gewaehlte Schriftgroesse, bevor irgendetwas angelegt wird
|
||||
|
||||
@@ -3765,6 +3808,70 @@ static void appendTuneLive(String& out, const char* kreis, uint32_t elapsedMs,
|
||||
// =====================================================================================
|
||||
// Live-Aktualisierung
|
||||
// =====================================================================================
|
||||
|
||||
// Zustandsband der Darstellung TD_BAND. Es beantwortet die haeufigste Frage - ist sie
|
||||
// bereit? - ohne dass eine Zahl gelesen werden muss.
|
||||
static void ready_band_update(const MachineState& st) {
|
||||
if (!g_readyBand || !g_readyBandLbl) return;
|
||||
const char* txt;
|
||||
uint32_t bg;
|
||||
lv_color_t fg;
|
||||
if (st.standbyActive) {
|
||||
txt = "Standby"; bg = 0x1e1e1e; fg = COL_TEXT_MUT;
|
||||
} else if (st.shotActive) {
|
||||
txt = "Bezug läuft"; bg = 0x3a2a05; fg = COL_ACCENT;
|
||||
} else if (st.cxActive) {
|
||||
txt = "Cold Extraction"; bg = 0x0d2b33; fg = lv_color_hex(0x80cbc4);
|
||||
} else {
|
||||
bool wOk = fabsf(st.tempW - st.setW) <= 1.0f;
|
||||
bool dOk = st.steamHeatDisabled || fabsf(st.tempD - st.setD) <= 3.0f;
|
||||
if (wOk && dOk) { txt = "Bereit — beide Kessel auf Temperatur"; bg = 0x11301a; fg = COL_OK; }
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// 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);
|
||||
if (lblShotTimer && g_shotFontNormal)
|
||||
lv_obj_set_style_text_font(lblShotTimer, on ? &lv_font_maven_pro_bold_96 : g_shotFontNormal, 0);
|
||||
if (lblBezug && g_bezugFontNormal)
|
||||
lv_obj_set_style_text_font(lblBezug, on ? &lv_font_maven_pro_bold_96 : g_bezugFontNormal, 0);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void ui_update(const MachineState& st) {
|
||||
if (!g_uiReady) return;
|
||||
|
||||
@@ -4074,14 +4181,14 @@ void ui_update(const MachineState& st) {
|
||||
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);
|
||||
lv_obj_set_style_text_color(lblTempW, temp_value_color(lv_color_hex(0x1976d2)), 0);
|
||||
label_set_if_changed(lblSetW, "Cold Extraction - Heizen aus");
|
||||
lv_obj_set_style_text_color(lblSetW, lv_color_hex(0x1976d2), 0);
|
||||
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_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);
|
||||
// Fortschritt = Ist/Soll in %, gedeckelt auf 100 (Ist >= Soll -> voll)
|
||||
@@ -4098,12 +4205,16 @@ void ui_update(const MachineState& st) {
|
||||
} else {
|
||||
lv_label_set_text(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_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);
|
||||
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) {
|
||||
}
|
||||
}
|
||||
// "BEREIT"-Pille im Header: Wasser im Zielband (und Dampf, sofern er heizt), kein
|
||||
// Fehler/Standby/Eco und kein laufender Aufheiz-Countdown. Mit Hysterese gegen
|
||||
@@ -4480,13 +4591,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;
|
||||
}
|
||||
|
||||
@@ -4495,8 +4606,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)
|
||||
@@ -4505,10 +4615,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++; }
|
||||
@@ -4526,10 +4633,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