From a4ed0197e757e9a4cf594334d1740163507a48cb Mon Sep 17 00:00:00 2001 From: raw-designs Date: Fri, 4 Sep 2026 12:06:24 +0200 Subject: [PATCH] fix(P4): Punkt am Kurvenende sichtbar, weiche Linienfuehrung (1.11.1) Das Diagramm beschnitt seine Kinder an der eigenen Kante: Der Punkt am rechten Ende war angeschnitten, und vom Schein war nichts zu sehen - damit blieb auch das Pulsieren unsichtbar. Punkt und Schein duerfen jetzt darueber hinausreichen; der Schein ist kraeftiger, der Punkt etwas groesser. Die Kurven verlaufen jetzt weich statt kantig. Gemessen wird weiterhin einmal je Sekunde, gezeichnet viermal so fein: Zwischen zwei Messwerten liegen vier Stuetzpunkte auf einem Kosinusbogen, der an den Messpunkten waagerecht beginnt und endet. Linienenden und -ecken sind abgerundet. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Wx7wu7uXXgRzp1zHUhtUa6 --- P4_Display_Firmware/Changelog.txt | 10 +++++ P4_Display_Firmware/config.h | 2 +- P4_Display_Firmware/ui.cpp | 72 +++++++++++++++++++++++++------ 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/P4_Display_Firmware/Changelog.txt b/P4_Display_Firmware/Changelog.txt index 90c4581..1e5bd70 100644 --- a/P4_Display_Firmware/Changelog.txt +++ b/P4_Display_Firmware/Changelog.txt @@ -1,3 +1,13 @@ +Version 1.11.1: +- Der Punkt am rechten Ende der Verlaufskurven war angeschnitten, und von seinem Schein war + nichts zu sehen: Das Diagramm beschnitt beides an seiner eigenen Kante. Punkt und Schein + dürfen jetzt darüber hinausreichen, das Pulsieren ist damit sichtbar. Der Schein wurde + zusätzlich kräftiger und der Punkt eine Spur größer. +- Die Linien verlaufen jetzt weich statt kantig. Gemessen wird weiterhin einmal je Sekunde, + gezeichnet wird viermal so fein: Zwischen zwei Messwerten liegen vier Stützpunkte auf + einem Kosinusbogen, der an den Messpunkten waagerecht beginnt und endet. Dadurch + entstehen dort keine Knicke mehr. Linienenden und -ecken sind zusätzlich abgerundet. + Version 1.11.0: - Die Verlaufskurven im Dashboard beginnen jetzt immer am selben Punkt: Der älteste Wert liegt links genau auf halber Höhe, die Kurve wächst von dort nach oben oder unten. Der diff --git a/P4_Display_Firmware/config.h b/P4_Display_Firmware/config.h index 5b4e3fe..cc004b3 100644 --- a/P4_Display_Firmware/config.h +++ b/P4_Display_Firmware/config.h @@ -126,7 +126,7 @@ // ------------------------------------------------------------------------------------- // Firmware // ------------------------------------------------------------------------------------- -#define DISPLAY_FW_VERSION "1.11.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) +#define DISPLAY_FW_VERSION "1.11.1" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) // ------------------------------------------------------------------------------------- // Protokoll diff --git a/P4_Display_Firmware/ui.cpp b/P4_Display_Firmware/ui.cpp index ae22a09..20fcc87 100644 --- a/P4_Display_Firmware/ui.cpp +++ b/P4_Display_Firmware/ui.cpp @@ -178,14 +178,19 @@ static void label_set_if_changed(lv_obj_t* lbl, const char* txt); // Definitio 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 +// Eine Minute Verlauf. Gemessen wird einmal je Sekunde, gezeichnet wird viermal so fein: +// Zwischen zwei Messwerten liegen vier Stuetzpunkte auf einem Kosinusbogen. Aus dem +// Streckenzug mit harten Ecken wird damit eine weich geschwungene Linie. +#define SPARK_TEILUNG 4 +#define DASH_SPARK_STEP_MS 250 +#define DASH_SPARK_POINTS (60 * SPARK_TEILUNG) // 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 // Punkt am juengsten Messwert. Er pulsiert leicht und traegt einen weichen Schein - das // ist die einzige Stelle der Oberflaeche, an der etwas dauerhaft in Bewegung ist, und // deshalb bleibt er klein und langsam. -#define SPARK_DOT_SIZE 12 +#define SPARK_DOT_SIZE 14 #define SPARK_DOT_PULS_MS 1100 static lv_obj_t* g_sparkW = nullptr; // Verlaufskurven der Darstellung TD_VERLAUF static lv_obj_t* g_sparkD = nullptr; @@ -1214,10 +1219,14 @@ static void spark_dot_place(lv_obj_t* ch, lv_chart_series_t* ser, lv_obj_t* dot, int32_t y = h - (int32_t)(((int64_t)(v - mn) * h) / (mx - mn)); if (y < r) y = r; if (y > h - r) y = h - r; - lv_obj_align(dot, LV_ALIGN_TOP_LEFT, w - r, y - r); // Mitte auf den letzten Punkt + lv_obj_align(dot, LV_ALIGN_TOP_LEFT, w - r, y - r); // Mitte auf dem letzten Punkt lv_obj_remove_flag(dot, LV_OBJ_FLAG_HIDDEN); } +// Gefilterte Sekundenprobe, vom Abtaster gesetzt und vom Schritt-Timer abgeholt. +static float g_probeW = 0.0f, g_probeD = 0.0f; +static bool g_probeGueltig = false; + static void spark_ranges_fit() { int32_t aW = 0, aD = 0, dW = 0, dD = 0; bool okW = spark_anker(g_sparkW, g_sparkSerW, &aW, &dW); @@ -1254,8 +1263,8 @@ static void spark_ranges_fit() { // Weiches Pulsieren des Punktes: Nur der Schein atmet, der Punkt selbst bleibt ruhig. static void spark_dot_puls_cb(void* var, int32_t v) { lv_obj_t* d = (lv_obj_t*)var; - lv_obj_set_style_shadow_width(d, 6 + v / 5, 0); // 6 .. 26 - lv_obj_set_style_shadow_opa(d, (lv_opa_t)(50 + v), 0); // 50 .. 150 + lv_obj_set_style_shadow_width(d, 8 + v / 3, 0); // 8 .. 41 + lv_obj_set_style_shadow_opa(d, (lv_opa_t)(60 + v + v / 2), 0); // 60 .. 210 } static void spark_dot_puls(lv_obj_t* dot) { lv_anim_t a; lv_anim_init(&a); @@ -1289,6 +1298,13 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t** 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); + // Runde Enden und Ecken: An den Stuetzpunkten sitzt damit kein spitzer Winkel. + lv_obj_set_style_line_rounded(ch, true, LV_PART_ITEMS); + // Der Punkt am juengsten Messwert sitzt am rechten Rand, sein Schein reicht darueber + // hinaus. Ohne diese Freigabe schneidet das Diagramm beides ab - der Punkt war + // angeschnitten, und vom Schein war ueberhaupt nichts zu sehen. + lv_obj_add_flag(ch, LV_OBJ_FLAG_OVERFLOW_VISIBLE); + lv_obj_add_flag(parent, LV_OBJ_FLAG_OVERFLOW_VISIBLE); // dasselbe fuer die Karte lv_obj_clear_flag(ch, LV_OBJ_FLAG_SCROLLABLE); *ser = lv_chart_add_series(ch, col, LV_CHART_AXIS_PRIMARY_Y); @@ -1301,8 +1317,9 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t** lv_obj_set_style_bg_color(*dot, lv_color_white(), 0); lv_obj_set_style_bg_opa(*dot, LV_OPA_COVER, 0); lv_obj_set_style_shadow_color(*dot, lv_color_white(), 0); - lv_obj_set_style_shadow_width(*dot, 10, 0); - lv_obj_set_style_shadow_opa(*dot, 90, 0); + lv_obj_set_style_shadow_width(*dot, 14, 0); + lv_obj_set_style_shadow_opa(*dot, 120, 0); + lv_obj_set_style_shadow_spread(*dot, 1, 0); lv_obj_remove_flag(*dot, LV_OBJ_FLAG_CLICKABLE); lv_obj_remove_flag(*dot, LV_OBJ_FLAG_SCROLLABLE); // Bis die erste Stelle berechnet ist, bleibt er verborgen - sonst blitzte er kurz @@ -2085,6 +2102,36 @@ static void chart_redraw() { } lv_chart_refresh(g_chart); } +// Vier Stuetzpunkte je Messwert. Der Weg von der vorigen zur neuen Probe wird nicht +// geradlinig genommen, sondern auf einem Kosinusbogen: Er beginnt und endet waagerecht, +// weshalb an den Messpunkten kein Knick entsteht. Genau das macht den runden Eindruck. +static void spark_step_cb(lv_timer_t*) { + if (!g_probeGueltig) return; + if (!g_sparkW && !g_sparkD) return; // andere Darstellung: nichts zu tun + + static float vonW = 0.0f, zielW = 0.0f, vonD = 0.0f, zielD = 0.0f; + static bool beginn = true; + static uint8_t schritt = SPARK_TEILUNG; + + if (beginn) { vonW = zielW = g_probeW; vonD = zielD = g_probeD; beginn = false; } + if (schritt >= SPARK_TEILUNG) { // neue Probe uebernehmen + vonW = zielW; zielW = g_probeW; + vonD = zielD; zielD = g_probeD; + schritt = 0; + } + schritt++; + const float t = (float)schritt / (float)SPARK_TEILUNG; + const float f = 0.5f - 0.5f * cosf(3.14159265f * t); + + if (g_sparkSerW && g_sparkW) + lv_chart_set_next_value(g_sparkW, g_sparkSerW, + (int32_t)((vonW + (zielW - vonW) * f) * 10.0f)); + if (g_sparkSerD && g_sparkD) + lv_chart_set_next_value(g_sparkD, g_sparkSerD, + (int32_t)((vonD + (zielD - vonD) * f) * 10.0f)); + spark_ranges_fit(); // gemeinsamer Massstab, Punkt am juengsten Wert nachfuehren +} + 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 @@ -2102,11 +2149,11 @@ static void chart_sample_cb(lv_timer_t*) { const float probeW = median_von(g_medW, g_medFill); const float probeD = median_von(g_medD, g_medFill); - 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 + // In die Kurven geschrieben wird nicht hier, sondern viermal je Sekunde im + // Schritt-Timer - dazwischen liegen die Stuetzpunkte des Kosinusbogens. + g_probeW = probeW; + g_probeD = probeD; + g_probeGueltig = true; if (!g_chart || !g_hist) return; // 1-s-Probe in die 60-min-Historie (Ringpuffer); Anzeige zeichnet chart_redraw @@ -4204,6 +4251,7 @@ void ui_init(ProtocolClient* client) { lv_timer_create(dash_timers_cb, 100, nullptr); // Timer-Anzeige fortlaufend aktualisieren lv_timer_create(eco_case_timer_cb, 5000, nullptr); // Eco/Gehaeuse alle 5 s wechseln lv_timer_create(chart_sample_cb, 1000, nullptr); // Verlauf jede Sekunde abtasten + lv_timer_create(spark_step_cb, DASH_SPARK_STEP_MS, nullptr); // Kurven weich fortschreiben show_page(PG_DASH); if (g_client) g_client->sendListProfiles(); -- 2.54.0