Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Opus 5 bcbeea2a17 fix(P4): Temperaturdelle in den Verlaufskurven nach dem Standby (1.10.3)
Direkt nach dem Aufwachen aus dem Standby zog die Wasserkurve eine kurze
Delle nach unten. Ursache ist die Messung, nicht der Kessel: In genau
diesem Moment schalten die Heizungen, und der Wassersensor haengt als NTC
am ADC. Ein Kessel kann binnen einer Sekunde nicht um Grade einbrechen.

Die Verlaufskurven halten deshalb rund um jeden Standby-Wechsel sechs
Sekunden lang ihren letzten Wert, statt die gestoerte Probe einzutragen.
Das gilt fuer die kleinen Kurven im Dashboard und fuer die grosse
Verlaufsseite. Die angezeigten Temperaturzahlen bleiben unveraendert.

Ausserdem: DISPLAY_FW_VERSION stand seit dem Ruecknehmen von 1.6.11 auf
1.6.10 fest und wurde bei keinem der folgenden Staende mitgezogen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fe8KxS47iPmuByJPs5RvEw
2026-09-04 09:16:18 +02:00
thomas 1fe96b70aa Merge pull request 'fix(P4): Dashboard-Sparklines wurden nie fortgeschrieben (1.10.2)' (#84) from fix/verlaufskurven-werden-nie-fortgeschrieben into main 2026-09-04 01:42:57 +02:00
raw-designsandClaude Opus 5 43517a2c48 fix(P4): Dashboard-Sparklines wurden nie fortgeschrieben (1.10.2)
Die Linien blieben identisch und unbewegt - kein Skalierungsproblem, wie
1.10.0 und 1.10.1 angenommen hatten.

lv_chart_set_next_value() für g_sparkW/g_sparkD stand am Ende von
chart_duty_btn_cb(), dem Callback des Heizleistungs-Umschalters auf der
Verlauf-SEITE. Die Sparklines wuchsen also nur, wenn jemand genau diesen
Knopf drückte. Ansonsten zeigten sie unverändert, was lv_chart_set_all_value()
beim Aufbau der Darstellung eingetragen hatte.

Der Block sitzt jetzt in chart_sample_cb() (1-s-Timer), und zwar vor dessen
"if (!g_chart || !g_hist) return" - die Dashboard-Kurven sollen nicht davon
abhängen, ob die Verlauf-Seite samt großem Diagramm aufgebaut ist.

Die gemeinsame Skala aus 1.10.1 bleibt richtig, konnte bisher aber nichts
ausrichten, weil nie neue Werte ankamen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gBXGt5M22ns5YfNJRaiMu
2026-09-04 01:42:35 +02:00
thomas 6a867e54fd Merge pull request 'fix(P4): Verlaufskurven mit gemeinsamer Skala - Höhen wieder vergleichbar (1.10.1)' (#83) from fix/verlauf-gemeinsame-skala into main 2026-09-03 17:23:29 +02:00
raw-designsandClaude Opus 5 edfa2d4711 fix(P4): Verlaufskurven mit gemeinsamer Skala - Höhen wieder vergleichbar (1.10.1)
Nachbesserung zu 1.10.0. Dort bekam jede Kurve ihren eigenen Ausschnitt aus
ihren eigenen min/max. Bei zwei flachen Kurven (Wasser 70 °C, Dampf 0 °C)
greift beidesmal die Mindestspanne, jede Linie sitzt mittig in ihrem Fenster -
und beide liegen auf identischer Höhe. Die Skalierung war zu clever: Bewegung
wurde sichtbar, aber die Höhe verlor jede Bedeutung.

- spark_range_fit(ch, ser) ersetzt durch spark_ranges_fit(): scannt beide
  Serien, bildet einen gemeinsamen Bereich und setzt ihn auf beide Charts.
- Der gemerkte Bereich liegt jetzt in zwei Datei-Statics statt in user_data
  der Charts; das nimmt auch das nicht freigegebene lv_malloc bei jedem
  Neuaufbau der Temperaturzeile weg.
- build_temp_row_content() setzt den Merker beim Aufbau zurück und ruft
  spark_ranges_fit() erst, wenn beide Kurven stehen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJ2iUTbm7Zz5DTPkwgJyC
2026-09-03 17:22:28 +02:00
thomas fc0a756127 Merge pull request 'fix(P4): Verlaufskurven skalieren nach Daten, Diagnose ausgebaut (1.10.0)' (#82) from fix/verlaufskurven-und-aufraeumen into main 2026-09-03 17:01:50 +02:00
raw-designsandClaude Opus 5 cc9ae945b1 fix(P4): Verlaufskurven skalieren nach Daten, Diagnose ausgebaut (1.10.0)
Die Sparklines im Dashboard lagen flach auf gleicher Höhe und bewegten sich
nicht, obwohl ein Kessel bei 80 °C stand und der andere bei 0.

Ursache: lv_chart_set_range() war an den Sollwert gebunden (soll-60 bis
soll+30 in Zehntelgrad). Liegt die Temperatur unter diesem Fenster - beim
Aufheizen oder bei abgeschaltetem Kessel -, klemmt LVGL jeden Punkt auf den
unteren Rand. Beide Kurven landeten dadurch auf derselben Linie.

- Neues spark_range_fit(): Ausschnitt aus min/max der vorhandenen Punkte
  (lv_chart_get_y_array), Mindestspanne DASH_SPARK_MIN_SPAN (40 = 4,0 °C),
  sonst ein Achtel Luft nach oben und unten.
- Der zuletzt gesetzte Bereich wird in user_data gemerkt, weil LVGL keinen
  Getter dafür hat; ohne den Vergleich würde set_range das Diagramm bei jeder
  Zustandsmeldung invalidieren.
- Die Charts füllen jetzt den Platz unterhalb der Zahlen (flex_grow 1,
  min_height 90) statt als 64-px-Streifen mittig zu hängen.

Aufräumen nach der abgeschlossenen Fehlersuche:
- Herzschlag, [Zustand]- und [Touch]-Messzeilen entfernt.
- refr_monitor_install() hinter WS7_PERF_MONITOR (aus).
- scroll_ohne_nachlauf() gibt LV_OBJ_FLAG_SCROLL_MOMENTUM wieder frei.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G95njDFkedFqXiz5sEaJ2
2026-09-03 17:01:19 +02:00
thomas 696707a9a4 Merge pull request 'fix(P4): Hintergrundbeleuchtung kommt sofort - Bridge braucht zwei Latches (1.9.12)' (#81) from fix/helligkeit-doppelte-uebernahme into main 2026-09-03 15:46:17 +02:00
4 changed files with 167 additions and 75 deletions
+49
View File
@@ -1,3 +1,52 @@
Version 1.10.3:
- Direkt nach dem Aufwachen aus dem Standby zog die Wasserkurve eine kurze Delle nach
unten. Ursache ist die Messung, nicht der Kessel: In genau diesem Moment schalten die
Heizungen, und der Wassersensor hängt als NTC am ADC. Ein Kessel kann binnen einer
Sekunde nicht um Grade einbrechen.
- Die Verlaufskurven halten deshalb rund um jeden Standby-Wechsel sechs Sekunden lang
ihren letzten Wert, statt die gestörte Probe einzutragen. Das gilt für die kleinen
Kurven im Dashboard und für die große Verlaufsseite.
- Die angezeigten Temperaturzahlen bleiben unverändert: Dort steht weiter, was gemessen
wird.
- Die Versionsanzeige stand seit dem Zurücknehmen von 1.6.11 auf 1.6.10 fest und wurde bei
keinem der folgenden Stände mitgezogen. Sie zeigt jetzt wieder den echten Stand.
Version 1.10.2:
- Die Verlaufskurven im Dashboard wurden überhaupt nicht fortgeschrieben. Der Code dafür
stand am Ende der Umschaltung „Heizleistung anzeigen" auf der Verlauf-Seite - also im
Callback eines Knopfes. Die Kurven wuchsen nur weiter, wenn jemand genau diesen Knopf
drückte. Sonst zeigten sie unverändert den Wert, der beim Aufbau der Darstellung gerade
anlag: zwei flache, gleich hohe Linien.
- Die Fortschreibung sitzt jetzt im sekündlichen Abtaster, dort, wo auch der große Verlauf
seine Messwerte nimmt. Damit bewegen sich die Kurven, und die gemeinsame Skala aus 1.10.1
kann ihre Arbeit überhaupt erst tun.
Version 1.10.1:
- Die beiden Verlaufskurven teilen sich jetzt eine gemeinsame Skala. In 1.10.0 zoomte jede
Kurve auf ihre eigenen Werte - dadurch saß jede mittig in ihrem eigenen Fenster, und
Wasser bei 70 Grad lag genauso hoch wie Dampf bei 0. Die Höhe sagte nichts mehr aus.
- Die Skala richtet sich nach den Werten beider Kessel zusammen. Der wärmere Kessel liegt
damit sichtbar höher, und die Skala bleibt so eng, wie es die Daten zulassen: Stehen beide
dicht beieinander, greift die Mindestspanne von vier Grad und schon Zehntelgrade sind zu
erkennen.
Version 1.10.0:
- Die Verlaufskurven im Dashboard zeigen jetzt wirklich einen Verlauf. Ihr Ausschnitt hing
bisher am Sollwert - sechs Grad darunter bis drei darüber. Das funktioniert nur, solange
der Kessel schon nahe am Ziel ist. Heizte er noch auf oder war er ganz aus, lag jeder
Messwert unterhalb des Fensters und wurde auf den unteren Rand geklemmt: Beide Kurven lagen
flach auf derselben Höhe und bewegten sich nicht, obwohl der eine Kessel bei 80 Grad stand
und der andere bei 0. Der Ausschnitt richtet sich jetzt nach den tatsächlich vorhandenen
Werten, mit einer Mindestspanne von vier Grad, damit ein ruhig geregelter Kessel nicht
jedes Zehntelgrad zum vollen Ausschlag aufbläht.
- Die Kurven nehmen jetzt den Platz unterhalb der Zahlen ein statt als schmaler Streifen in
der Mitte zu hängen: Sie beginnen weiter unten auf der Karte und sind deutlich höher.
- Der Nachlauf beim Wischen ist zurück. Er war abgeschaltet, weil jedes Einzelbild während
des Schwungs den ganzen Bildschirm neu zeichnete; seit die Anzeigewerte feste Maße haben,
fällt das weg.
- Die Messzeilen der Fehlersuche sind wieder entfernt. Die Messung des Bildaufbaus bleibt
erhalten, liegt aber hinter dem Schalter WS7_PERF_MONITOR und ist aus.
Version 1.9.12: Version 1.9.12:
- Die Hintergrundbeleuchtung kommt beim Wechsel in den und aus dem Standby jetzt sofort. - Die Hintergrundbeleuchtung kommt beim Wechsel in den und aus dem Standby jetzt sofort.
Bisher stand das Bild bereits fertig da - im Gegenlicht war es zu sehen -, blieb aber Bisher stand das Bild bereits fertig da - im Gegenlicht war es zu sehen -, blieb aber
@@ -47,23 +47,10 @@ static bool g_bootCountCleared = false;
// --- Protokoll-Callbacks --------------------------------------------------------------- // --- Protokoll-Callbacks ---------------------------------------------------------------
static void onState(const MachineState& st) { static void onState(const MachineState& st) {
// Vermessen: Zwischen dem Umschalten und dem sichtbaren Ergebnis vergehen rund fuenf
// Sekunden. Jede Station bekommt eine Zeitmarke, damit die Luecke zu sehen ist.
static bool s_lastStandby = false;
bool wechsel = (st.standbyActive != s_lastStandby);
s_lastStandby = st.standbyActive;
uint32_t t0 = millis();
if (wechsel) esp_rom_printf("[Zustand] %u ms: Standby jetzt %s - Meldung angekommen\n",
(unsigned)t0, st.standbyActive ? "an" : "aus");
// LVGL laeuft im realen Modus in einem eigenen Task -> UI-Zugriff sperren. // LVGL laeuft im realen Modus in einem eigenen Task -> UI-Zugriff sperren.
hal_lock(); hal_lock();
uint32_t t1 = millis();
ui_update(st); ui_update(st);
uint32_t t2 = millis();
hal_unlock(); hal_unlock();
if (wechsel || (t2 - t0) > 200)
esp_rom_printf("[Zustand] %u ms: Sperre %u ms, Anzeige aktualisiert %u ms\n",
(unsigned)millis(), (unsigned)(t1 - t0), (unsigned)(t2 - t1));
// Display im Standby aus; ist "Uhrzeit im Standby anzeigen" aktiv, bleibt es an, // Display im Standby aus; ist "Uhrzeit im Standby anzeigen" aktiv, bleibt es an,
// aber abgedunkelt (nicht blendend bei Nacht). Helligkeiten kommen von der S3 // aber abgedunkelt (nicht blendend bei Nacht). Helligkeiten kommen von der S3
@@ -85,16 +72,6 @@ static void onState(const MachineState& st) {
} }
} }
// Herzschlag aus dem Haupt-Ablauf. Zusammen mit dem Herzschlag aus dem Zeichen-Ablauf
// laesst sich unterscheiden, welcher der beiden stehen bleibt, wenn nichts mehr geht.
static void main_heartbeat() {
static uint32_t s_last = 0;
if (millis() - s_last < 2000) return;
s_last = millis();
esp_rom_printf("[Haupt] %u ms: Herzschlag, freier Speicher %u Byte\n",
(unsigned)millis(), (unsigned)ESP.getFreeHeap());
}
static void onAck(uint32_t id, bool ok, const String& message) { static void onAck(uint32_t id, bool ok, const String& message) {
// Automatische/Heartbeat-Acks nicht als Toast zeigen (pong, Handshake, Listen-Refresh). // Automatische/Heartbeat-Acks nicht als Toast zeigen (pong, Handshake, Listen-Refresh).
bool routine = ok && (message == "pong" || message == "hello" || bool routine = ok && (message == "pong" || message == "hello" ||
@@ -372,7 +349,6 @@ static unsigned long s_lastDbgMs = 0;
#endif #endif
void loop() { void loop() {
main_heartbeat();
g_client.loop(); // UART lesen, hello/heartbeat, state -> UI g_client.loop(); // UART lesen, hello/heartbeat, state -> UI
hal_loop(); // realer Modus: leer; Stub-Modus: lv_timer_handler() hal_loop(); // realer Modus: leer; Stub-Modus: lv_timer_handler()
+1 -1
View File
@@ -126,7 +126,7 @@
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// Firmware // Firmware
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.6.10" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) #define DISPLAY_FW_VERSION "1.10.3" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// Protokoll // Protokoll
+117 -50
View File
@@ -179,6 +179,9 @@ enum TempDesign { TD_GEWICHTET = 0, TD_VERLAUF, TD_FLAECHEN, TD_BAND, TD_DESIGN_
static uint8_t g_tempDesign = TD_BAND; static uint8_t g_tempDesign = TD_BAND;
static bool g_tempColored = false; // Temperaturzahl in der Zustandsfarbe statt weiss static bool g_tempColored = false; // Temperaturzahl in der Zustandsfarbe statt weiss
#define DASH_SPARK_POINTS 60 // eine Minute Verlauf, ein Punkt je Sekunde #define DASH_SPARK_POINTS 60 // eine Minute Verlauf, ein Punkt je Sekunde
// Kleinster Ausschnitt der Verlaufskurve in Zehntelgrad. Ohne Untergrenze blaeht ein
// ruhig geregelter Kessel jedes Zehntelgrad zum vollen Ausschlag auf.
#define DASH_SPARK_MIN_SPAN 40 // 4,0 Grad
static lv_obj_t* g_sparkW = nullptr; // Verlaufskurven der Darstellung TD_VERLAUF static lv_obj_t* g_sparkW = nullptr; // Verlaufskurven der Darstellung TD_VERLAUF
static lv_obj_t* g_sparkD = nullptr; static lv_obj_t* g_sparkD = nullptr;
static lv_chart_series_t* g_sparkSerW = nullptr; static lv_chart_series_t* g_sparkSerW = nullptr;
@@ -719,13 +722,10 @@ static void clock_size_apply() {
if (g_standbyClock) lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0); if (g_standbyClock) lv_obj_set_style_text_font(g_standbyClock, clock_font(), 0);
} }
static void scroll_ohne_nachlauf(lv_obj_t* o) { // Der Nachlauf beim Wischen war auf dem Waveshare-Panel eine Zeit lang abgeschaltet, weil
#if JC_PANEL_TYPE == WS_PANEL_7H // jedes Einzelbild waehrend des Schwungs den ganzen Schirm neu zeichnete. Seit die
lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLL_MOMENTUM); // Anzeigewerte feste Masse haben, faellt das weg - das Menue laeuft mit Nachlauf fluessig.
#else static void scroll_ohne_nachlauf(lv_obj_t* o) { (void)o; }
(void)o;
#endif
}
static lv_obj_t* make_page(lv_obj_t* parent) { static lv_obj_t* make_page(lv_obj_t* parent) {
lv_obj_t* p = lv_obj_create(parent); lv_obj_t* p = lv_obj_create(parent);
@@ -931,12 +931,7 @@ static bool wake_choice_offered() {
(g_state.cxEnabled && g_state.cxArmable && g_state.cxAskOnWake); (g_state.cxEnabled && g_state.cxArmable && g_state.cxAskOnWake);
} }
static void standby_screen_cb(lv_event_t*) { // Tipp auf Standby-Uhr -> nachfragen static void standby_screen_cb(lv_event_t*) { // Tipp auf Standby-Uhr -> nachfragen
// Vermessen: Vom Tippen bis zur sichtbaren Rueckfrage vergehen rund fuenf Sekunden.
uint32_t t0 = millis();
esp_rom_printf("[Touch] %u ms: Tipp auf die Standby-Uhr erkannt\n", (unsigned)t0);
wake_confirm_show(); wake_confirm_show();
esp_rom_printf("[Touch] %u ms: Rueckfrage aufgebaut, brauchte %u ms\n",
(unsigned)millis(), (unsigned)(millis() - t0));
} }
static void wake_cancel() { // Abbrechen -> zurueck in die Standby-Helligkeit static void wake_cancel() { // Abbrechen -> zurueck in die Standby-Helligkeit
wake_timeout_stop(); wake_timeout_stop();
@@ -1113,10 +1108,77 @@ 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 // 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 // eigentliche Frage an eine PID-Regelung, und eine Zahl kann sie grundsaetzlich nicht
// beantworten. Gespeist wird sie aus derselben Sekundenabtastung wie die Verlaufsseite. // beantworten. Gespeist wird sie aus derselben Sekundenabtastung wie die Verlaufsseite.
// Gemeinsamer Ausschnitt fuer BEIDE Verlaufskurven.
//
// Zwei Anlaeufe waren vorher daneben. Erst hing der Ausschnitt am Sollwert - dann lag beim
// Aufheizen jeder Messwert unterhalb des Fensters und wurde auf den unteren Rand geklemmt.
// Danach zoomte jede Kurve auf ihre eigenen Werte - dann sass jede mittig in ihrem eigenen
// Fenster, und Wasser bei 70 Grad lag genauso hoch wie Dampf bei 0. Die Hoehe sagte nichts.
//
// Jetzt teilen sich beide Kurven eine Skala, die sich nach den Werten beider zusammen
// richtet. Damit ist die Hoehe wieder vergleichbar - der waermere Kessel liegt sichtbar
// hoeher -, und die Skala bleibt so eng, wie es die Daten zulassen: Stehen beide Kessel
// dicht beieinander, greift die Mindestspanne und schon Zehntelgrade sind zu sehen.
static int32_t g_sparkMin = INT32_MIN; // zuletzt gesetzter Ausschnitt, fuer beide gleich
static int32_t g_sparkMax = INT32_MIN;
// Kurz nach einem Standby-Wechsel sackt die Wassertemperatur fuer einen Sekundenbruchteil
// ab. In genau diesem Moment schalten die Heizungen, und der Wassersensor ist ein NTC am
// ADC - ein Kessel kann binnen einer Sekunde nicht um Grade einbrechen, die Delle ist also
// eine Stoerung der Messung, kein Vorgang an der Maschine. Fuer die Dauer des Uebergangs
// schreiben die Verlaufskurven deshalb den letzten Wert von vor dem Wechsel weiter. Die
// angezeigten Zahlen bleiben unangetastet - was gemessen wird, steht dort weiter drin.
#define KURVEN_SPERRE_MS 6000
static uint32_t g_kurvenSperreBis = 0; // millis(); bis dahin Werte halten
static float g_haltW = 0.0f;
static float g_haltD = 0.0f;
static void spark_scan(lv_obj_t* ch, lv_chart_series_t* ser, int32_t* mn, int32_t* mx) {
if (!ch || !ser) return;
int32_t* pts = lv_chart_get_y_array(ch, ser);
if (!pts) return;
uint32_t n = lv_chart_get_point_count(ch);
for (uint32_t i = 0; i < n; i++) {
if (pts[i] == LV_CHART_POINT_NONE) continue;
if (pts[i] < *mn) *mn = pts[i];
if (pts[i] > *mx) *mx = pts[i];
}
}
static void spark_ranges_fit() {
int32_t mn = INT32_MAX, mx = INT32_MIN;
spark_scan(g_sparkW, g_sparkSerW, &mn, &mx);
spark_scan(g_sparkD, g_sparkSerD, &mn, &mx);
if (mn > mx) return; // noch kein einziger Wert
if (mx - mn < DASH_SPARK_MIN_SPAN) { // beide dicht beieinander: mittig halten
int32_t mitte = (mn + mx) / 2;
mn = mitte - DASH_SPARK_MIN_SPAN / 2;
mx = mitte + DASH_SPARK_MIN_SPAN / 2;
} else { // sonst etwas Luft nach oben und unten
int32_t luft = (mx - mn) / 8;
mn -= luft;
mx += luft;
}
// Nur bei echter Aenderung setzen - lv_chart_set_range macht das Diagramm sonst bei
// jeder Zustandsmeldung ungueltig, auch wenn sich der Ausschnitt gar nicht ruehrt.
// Abfragen laesst sich der gesetzte Ausschnitt bei LVGL nicht, also wird er gemerkt.
if (mn == g_sparkMin && mx == g_sparkMax) return;
g_sparkMin = mn;
g_sparkMax = mx;
if (g_sparkW) lv_chart_set_range(g_sparkW, LV_CHART_AXIS_PRIMARY_Y, mn, mx);
if (g_sparkD) lv_chart_set_range(g_sparkD, LV_CHART_AXIS_PRIMARY_Y, mn, mx);
}
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) { float jetzt, float soll) {
lv_obj_t* ch = lv_chart_create(parent); lv_obj_t* ch = lv_chart_create(parent);
lv_obj_set_size(ch, LV_PCT(96), 64); // Die Kurve nimmt den Platz unterhalb der Zahlen ein, statt als schmaler Streifen in
// der Mitte zu haengen. Sie beginnt damit weiter unten auf der Karte und hat deutlich
// mehr Hoehe - Temperaturaenderungen sind so ueberhaupt erst zu erkennen.
lv_obj_set_width(ch, LV_PCT(96));
lv_obj_set_height(ch, LV_PCT(100));
lv_obj_set_flex_grow(ch, 1);
lv_obj_set_style_min_height(ch, 90, 0);
lv_chart_set_type(ch, LV_CHART_TYPE_LINE); lv_chart_set_type(ch, LV_CHART_TYPE_LINE);
lv_chart_set_point_count(ch, DASH_SPARK_POINTS); lv_chart_set_point_count(ch, DASH_SPARK_POINTS);
lv_chart_set_div_line_count(ch, 0, 0); lv_chart_set_div_line_count(ch, 0, 0);
@@ -1130,13 +1192,12 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t**
lv_obj_clear_flag(ch, LV_OBJ_FLAG_SCROLLABLE); lv_obj_clear_flag(ch, LV_OBJ_FLAG_SCROLLABLE);
*ser = lv_chart_add_series(ch, col, LV_CHART_AXIS_PRIMARY_Y); *ser = lv_chart_add_series(ch, col, LV_CHART_AXIS_PRIMARY_Y);
// Ausschnitt und Startwert sofort setzen. Ein frisch angelegtes Diagramm hat lauter // Startwert sofort setzen. Ein frisch angelegtes Diagramm hat lauter "kein Wert"-Punkte
// "kein Wert"-Punkte und zeichnet deshalb nichts - beim Umschalten der Darstellung // und zeichnet deshalb nichts - beim Umschalten der Darstellung saehe man bis zur
// saehe man bis zur ersten Sekundenprobe eine leere Flaeche. // ersten Sekundenprobe eine leere Flaeche.
int32_t sollT = (int32_t)(soll * 10.0f); (void)soll;
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)); lv_chart_set_all_value(ch, *ser, (int32_t)(jetzt * 10.0f));
return ch; return ch; // Ausschnitt setzt spark_ranges_fit(), sobald beide Kurven stehen
} }
// Temperaturbereich gemaess aktiver Darstellung (neu) aufbauen - auch beim Umschalten // Temperaturbereich gemaess aktiver Darstellung (neu) aufbauen - auch beim Umschalten
@@ -1149,6 +1210,7 @@ static void build_temp_row_content() {
bulbTempW = bulbTempD = nullptr; bulbTempW = bulbTempD = nullptr;
g_sparkW = g_sparkD = nullptr; g_sparkW = g_sparkD = nullptr;
g_sparkSerW = g_sparkSerD = nullptr; g_sparkSerW = g_sparkSerD = nullptr;
g_sparkMin = g_sparkMax = INT32_MIN; // gemerkter Ausschnitt gilt fuer die alten Kurven
g_readyBand = g_readyBandLbl = nullptr; g_readyBand = g_readyBandLbl = nullptr;
const lv_color_t teal = lv_color_hex(0x80cbc4); const lv_color_t teal = lv_color_hex(0x80cbc4);
@@ -1177,6 +1239,7 @@ static void build_temp_row_content() {
lv_obj_t* cd = tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD, lv_obj_t* cd = tempField(g_tempRow, "DAMPF", teal, &lblTempD, &lblSetD, &barDutyD,
1, &lv_font_maven_pro_bold_96, false, 0); 1, &lv_font_maven_pro_bold_96, false, 0);
g_sparkD = tempSpark(cd, COL_ACCENT, &g_sparkSerD, g_state.tempD, g_state.setD); g_sparkD = tempSpark(cd, COL_ACCENT, &g_sparkSerD, g_state.tempD, g_state.setD);
spark_ranges_fit();
break; break;
} }
@@ -1675,20 +1738,6 @@ static void chart_duty_btn_cb(lv_event_t*) {
else lv_obj_add_flag(g_legDuty, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(g_legDuty, LV_OBJ_FLAG_HIDDEN);
} }
chart_redraw(); 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) { static void build_chart(lv_obj_t* p) {
lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE); lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE);
@@ -1913,11 +1962,31 @@ static void chart_redraw() {
lv_chart_refresh(g_chart); lv_chart_refresh(g_chart);
} }
static void chart_sample_cb(lv_timer_t*) { static void chart_sample_cb(lv_timer_t*) {
// Verlaufskurven des Dashboards (Darstellung "Verlauf") fortschreiben. Das stand
// frueher am Ende von chart_duty_btn_cb - also im Callback eines Knopfes auf der
// Verlauf-SEITE. Die Kurven wurden dadurch nur fortgeschrieben, wenn jemand genau
// diesen Knopf drueckte; sonst zeigten sie unveraendert den Wert, der beim Aufbau
// der Darstellung gerade anlag. Beide Linien lagen deshalb flach und identisch.
//
// Bewusst vor dem Ausstieg unten: Die Kurven im Dashboard haengen nicht daran, ob
// die Verlauf-Seite mit ihrem grossen Diagramm schon aufgebaut ist.
// Sperrfrist nach einem Standby-Wechsel: letzten Wert halten statt der frischen Probe.
bool sperre = g_kurvenSperreBis != 0 && (int32_t)(millis() - g_kurvenSperreBis) < 0;
if (!sperre) { g_haltW = g_state.tempW; g_haltD = g_state.tempD; }
const float probeW = sperre ? g_haltW : g_state.tempW;
const float probeD = sperre ? g_haltD : g_state.tempD;
if (g_sparkSerW && g_sparkW)
lv_chart_set_next_value(g_sparkW, g_sparkSerW, (int32_t)(probeW * 10.0f));
if (g_sparkSerD && g_sparkD)
lv_chart_set_next_value(g_sparkD, g_sparkSerD, (int32_t)(probeD * 10.0f));
spark_ranges_fit(); // gemeinsame Skala, damit die Hoehen vergleichbar bleiben
if (!g_chart || !g_hist) return; if (!g_chart || !g_hist) return;
// 1-s-Probe in die 60-min-Historie (Ringpuffer); Anzeige zeichnet chart_redraw // 1-s-Probe in die 60-min-Historie (Ringpuffer); Anzeige zeichnet chart_redraw
bool caseOn = g_state.caseSensorEnabled && g_state.hasCaseTemp; bool caseOn = g_state.caseSensorEnabled && g_state.hasCaseTemp;
g_hist->w[g_histPos] = (int16_t)(g_state.tempW * 10.0f + 0.5f); g_hist->w[g_histPos] = (int16_t)(probeW * 10.0f + 0.5f);
g_hist->d[g_histPos] = (int16_t)(g_state.tempD * 10.0f + 0.5f); g_hist->d[g_histPos] = (int16_t)(probeD * 10.0f + 0.5f);
g_hist->c[g_histPos] = caseOn ? (int16_t)(g_state.caseTemp * 10.0f + 0.5f) : CHART_HIST_NONE; g_hist->c[g_histPos] = caseOn ? (int16_t)(g_state.caseTemp * 10.0f + 0.5f) : CHART_HIST_NONE;
float dw = g_state.dutyW, dd = g_state.dutyD; float dw = g_state.dutyW, dd = g_state.dutyD;
g_hist->dutyW[g_histPos] = (uint8_t)(dw < 0.0f ? 0 : (dw > 100.0f ? 100 : (int)(dw + 0.5f))); g_hist->dutyW[g_histPos] = (uint8_t)(dw < 0.0f ? 0 : (dw > 100.0f ? 100 : (int)(dw + 0.5f)));
@@ -3573,13 +3642,6 @@ static void refr_ready_cb(lv_event_t*) {
(int)g_refrArea[i].y1, (int)g_refrArea[i].y2); (int)g_refrArea[i].y1, (int)g_refrArea[i].y2);
} }
static void ui_heartbeat_cb(lv_timer_t*) {
lv_mem_monitor_t m; lv_mem_monitor(&m);
esp_rom_printf("[UI] %u ms: Herzschlag, LVGL-Speicher frei %u von %u Byte, groesster Block %u\n",
(unsigned)millis(), (unsigned)m.free_size, (unsigned)m.total_size,
(unsigned)m.free_biggest_size);
}
// LVGL legt fuer JEDE Textglyphe einen eigenen Zwischenpuffer an - Breite mal Hoehe, ein // LVGL legt fuer JEDE Textglyphe einen eigenen Zwischenpuffer an - Breite mal Hoehe, ein
// Byte je Bildpunkt. Bei der grossen Standby-Uhr sind das 190 x 256 = rund 48 KB pro Ziffer, // Byte je Bildpunkt. Bei der grossen Standby-Uhr sind das 190 x 256 = rund 48 KB pro Ziffer,
// und mit zwei parallel arbeitenden Zeicheneinheiten fast 100 KB gleichzeitig. Der interne // und mit zwei parallel arbeitenden Zeicheneinheiten fast 100 KB gleichzeitig. Der interne
@@ -4024,8 +4086,9 @@ void ui_init(ProtocolClient* client) {
lv_draw_buf_handlers_t* fh = lv_draw_buf_get_font_handlers(); lv_draw_buf_handlers_t* fh = lv_draw_buf_get_font_handlers();
fh->buf_malloc_cb = font_buf_malloc; fh->buf_malloc_cb = font_buf_malloc;
fh->buf_free_cb = font_buf_free; fh->buf_free_cb = font_buf_free;
refr_monitor_install(); #if WS7_PERF_MONITOR
lv_timer_create(ui_heartbeat_cb, 2000, nullptr); refr_monitor_install(); // nur zum Einmessen, siehe WS7_PERF_MONITOR
#endif
g_uiReady = true; g_uiReady = true;
} }
@@ -4212,19 +4275,23 @@ void ui_update(const MachineState& st) {
// Standby-Uhr-Overlay (Backlight wird in onState gesteuert). Uebergaenge werden weich // Standby-Uhr-Overlay (Backlight wird in onState gesteuert). Uebergaenge werden weich
// gefadet: Einschlafen blendet die Uhr ein, Aufwecken blendet sie aus. Nur bei echtem // gefadet: Einschlafen blendet die Uhr ein, Aufwecken blendet sie aus. Nur bei echtem
// Statuswechsel animieren (g_standbyShown), sonst nur Uhrzeit/Vordergrund aktualisieren. // Statuswechsel animieren (g_standbyShown), sonst nur Uhrzeit/Vordergrund aktualisieren.
// Standby-Wechsel merken: die Verlaufskurven halten danach kurz ihren letzten Wert,
// damit die Messstoerung beim Schalten der Heizungen keine Delle in die Linie zieht.
static bool letzterStandby = false;
if (st.standbyActive != letzterStandby) {
letzterStandby = st.standbyActive;
g_haltW = st.tempW;
g_haltD = st.tempD;
g_kurvenSperreBis = millis() + KURVEN_SPERRE_MS;
}
if (g_standbyScreen) { if (g_standbyScreen) {
bool shouldShow = st.standbyActive && st.standbyShowClock; bool shouldShow = st.standbyActive && st.standbyShowClock;
if (shouldShow) { if (shouldShow) {
label_set_if_changed(g_standbyClock, zeit.length() ? zeit.c_str() : "--:--"); label_set_if_changed(g_standbyClock, zeit.length() ? zeit.c_str() : "--:--");
if (!g_standbyShown) { if (!g_standbyShown) {
// Vermessen: Das Einblenden der Uhr ist der Moment, in dem das Display
// zuletzt stehen geblieben ist. Bleibt die zweite Zeile aus, haengt es hier.
esp_rom_printf("[UI] Standby-Uhr wird eingeblendet\n");
uint32_t t0 = millis();
ui_fade_in(g_standbyScreen, UI_FADE_MS); // Einschlaf-Animation ui_fade_in(g_standbyScreen, UI_FADE_MS); // Einschlaf-Animation
g_standbyShown = true; g_standbyShown = true;
esp_rom_printf("[UI] Standby-Uhr eingeblendet, %u ms\n",
(unsigned)(millis() - t0));
} }
// Frueher wurde die Uhr hier bei JEDER Zustandsmeldung nach vorn geholt. Das // Frueher wurde die Uhr hier bei JEDER Zustandsmeldung nach vorn geholt. Das
// ordnet die Ebenen neu und laesst LVGL den gesamten Bildschirm neu zeichnen - // ordnet die Ebenen neu und laesst LVGL den gesamten Bildschirm neu zeichnen -