Compare commits

..
Author SHA1 Message Date
raw-designsandClaude Opus 5 9fef4f8a6e fix(P4): Aufweckdialog sofort statt nach fünf Sekunden (1.8.7)
Vom Fingertipp auf die Standby-Uhr bis zum Dialog vergingen ~5 s mit
schwarzem Bildschirm. Ursache war der Scrim mit LV_OPA_70 über den ganzen
Bildschirm: Ein durchscheinender Vollbild-Layer zwingt LVGL zu einem
Zwischenpuffer und zum Mischen der gesamten Fläche - bei 1280x720 in 24 Bit
die teuerste Operation überhaupt.

- Wake-Scrim auf WS_PANEL_7H jetzt LV_OPA_COVER. Verloren geht nichts: Im
  Standby liegt dahinter ohnehin nur Schwarz.
- Der Drawer-Scrim (LV_OPA_50) hat dasselbe Problem und ist ebenfalls
  deckend, in 0x0d0d0d - auf dunklem Grund optisch praktisch identisch.
- Die JC-Panels behalten in beiden Fällen die Transparenz.
- Die Dialogkarte wächst mit der Schriftgröße (420 / 640 / 900 px), damit
  der Text nicht an unglücklichen Stellen umbricht.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:47:16 +02:00
thomas 0f5b681421 Merge pull request 'feat(P4): Uhr läuft lokal weiter, wenn die S3 keine Zeit liefert (1.8.6)' (#66) from feat/uhr-laeuft-lokal-weiter into main 2026-09-03 00:34:07 +02:00
raw-designsandClaude Opus 5 01d960258a feat(P4): Uhr läuft lokal weiter, wenn die S3 keine Zeit liefert (1.8.6)
Die Uhrzeit kommt von der Hauptplatine und ist leer, solange dort keine
Zeitquelle synchronisiert ist. Bei einem erneuten Abgleich ist sie kurz weg,
und die Uhr verschwand für ein paar Sekunden - im Standby unruhig.

Der P4 merkt sich jetzt die zuletzt empfangene Zeit (Minuten seit
Mitternacht plus lv_tick zum Empfangszeitpunkt) und rechnet zwischen den
Meldungen selbst weiter. Sobald die S3 wieder eine Zeit schickt, gilt deren
Wert - der P4 stellt nichts richtig und ersetzt keine Zeitquelle, er
überbrückt nur die Lücke. Ohne je empfangene Zeit bleibt die Anzeige leer
wie bisher.

Gilt für die Kopfleisten-Uhr und die große Standby-Uhr; beide setzen den
Text jetzt über label_set_if_changed, sparen also unnötige Neuzeichnungen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:33:50 +02:00
thomas 3c333f0280 Merge pull request 'fix(P4): Standby-Verzögerung, Dialog-Timeout, Kurvenhintergrund, Bindestrich (1.8.5)' (#65) from fix/standby-verzoegerung-und-feinschliff into main 2026-09-03 00:26:30 +02:00
raw-designsandClaude Opus 5 b6c6214b41 fix(P4): Standby-Verzögerung, Dialog-Timeout, Kurvenhintergrund, Bindestrich (1.8.5)
- Die ~2 s Verzögerung rund um den Standby lag nicht an der Helligkeit:
  lv_obj_move_foreground(g_standbyScreen) lief bei JEDER Zustandsmeldung.
  Das ordnet die Ebenen neu und lässt LVGL den ganzen Bildschirm neu
  zeichnen - einmal pro Sekunde, 1280x720 in 24 Bit. Nach vorn geholt wird
  die Uhr jetzt nur beim Einblenden.
- Der Aufweckdialog schließt sich nach 30 s ohne Auswahl selbst und wird wie
  Abbrechen gewertet (lv_timer, repeat_count 1). Sonst bliebe das Display
  hell stehen, weil der Dialog auf normale Helligkeit schaltet.
- Die Verlaufskurve hat keinen eigenen Hintergrund mehr. Die abgesetzte
  Fläche war nur nötig, solange die Kurve vor dem ersten Messwert leer war -
  seit sie beim aktuellen Wert startet, ist immer eine Linie da.
- Statistik: Bei fehlendem Gewicht stand ein En-Dash, den der Zeichensatz
  nicht enthält - erschien als leeres Rechteck. Jetzt ASCII-Bindestrich.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STPpZoK3ToDhrcniVqE6MJ
2026-09-03 00:26:12 +02:00
thomas 63290afa07 Merge pull request 'fix(P4): Übergänge ohne Animation, kein blaues Aufblitzen, Temperaturfarbe wählbar (1.8.4)' (#64) from fix/uebergaenge-und-temperaturfarbe into main 2026-09-03 00:04:43 +02:00
2 changed files with 138 additions and 12 deletions
+39
View File
@@ -1,3 +1,42 @@
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
+99 -12
View File
@@ -676,7 +676,9 @@ static void stat_list_set(int i, const char* dt, float d, float w, bool kalt) {
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], "");
// 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" : "");
}
@@ -864,6 +866,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;
@@ -873,6 +887,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
@@ -886,12 +903,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.
@@ -907,6 +931,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");
@@ -1044,9 +1069,7 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t**
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_bg_opa(ch, LV_OPA_TRANSP, 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
@@ -3530,8 +3553,16 @@ 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);
#if JC_PANEL_TYPE == WS_PANEL_7H
// Deckend statt halbdurchsichtig - aus demselben Grund wie beim Aufweckdialog: Ein
// durchscheinender Vorhang laesst LVGL die ganze Flaeche darunter mitzeichnen und
// mischen. Auf dunklem Grund sieht ein dunkles Deckend praktisch genauso aus.
lv_obj_set_style_bg_color(g_drawer, lv_color_hex(0x0d0d0d), 0);
lv_obj_set_style_bg_opa(g_drawer, LV_OPA_COVER, 0);
#else
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);
#endif
lv_obj_set_style_border_width(g_drawer, 0, 0);
lv_obj_set_style_pad_all(g_drawer, 0, 0);
lv_obj_set_style_radius(g_drawer, 0, 0);
@@ -3611,13 +3642,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);
@@ -3872,6 +3915,44 @@ static void dash_brew_mode_tick(const MachineState& st) {
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) {
if (!g_uiReady) return;
@@ -3888,10 +3969,13 @@ void ui_update(const MachineState& st) {
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);
// 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);
@@ -3904,13 +3988,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;