diff --git a/P4_Display_Firmware/Changelog.txt b/P4_Display_Firmware/Changelog.txt index 1b7488e..ca475df 100644 --- a/P4_Display_Firmware/Changelog.txt +++ b/P4_Display_Firmware/Changelog.txt @@ -1,3 +1,15 @@ +Version 1.10.4: +- Die Maßnahme aus 1.10.3 machte die Delle nach dem Standby länger statt kleiner. Sie + hielt beim Umschalten des Standby-Merkers den zuletzt gemeldeten Wert fest - zu diesem + Zeitpunkt lag die gestörte Probe aber bereits an. Festgehalten wurde also ausgerechnet + die Delle, und das sekundenlang. +- Statt einer Sperrfrist tragen die Verlaufskurven jetzt den mittleren Wert der letzten + fünf Proben ein (Median). Eine oder zwei verirrte Proben verschwinden damit vollständig, + unabhängig davon, wann sie auftreten - beim Standby-Ende ebenso wie beim Bezugsstart. +- Echte Temperaturänderungen kommen unverfälscht durch, nur zwei Sekunden später. Das ist + die Verzögerung, um die es hier ging. +- Die angezeigten Temperaturzahlen bleiben weiterhin ungefiltert. + 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 diff --git a/P4_Display_Firmware/config.h b/P4_Display_Firmware/config.h index 8653d6a..5a86062 100644 --- a/P4_Display_Firmware/config.h +++ b/P4_Display_Firmware/config.h @@ -126,7 +126,7 @@ // ------------------------------------------------------------------------------------- // Firmware // ------------------------------------------------------------------------------------- -#define DISPLAY_FW_VERSION "1.10.3" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) +#define DISPLAY_FW_VERSION "1.10.4" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) // ------------------------------------------------------------------------------------- // Protokoll diff --git a/P4_Display_Firmware/ui.cpp b/P4_Display_Firmware/ui.cpp index 8de0dce..a472f46 100644 --- a/P4_Display_Firmware/ui.cpp +++ b/P4_Display_Firmware/ui.cpp @@ -1124,14 +1124,37 @@ 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; +// ADC, einmal je Regelzyklus abgetastet - ein Kessel kann binnen einer Sekunde nicht um +// Grade einbrechen, die Delle ist also eine Stoerung der Messung, kein Vorgang an der +// Maschine. +// +// In die Kurven geht deshalb nicht die frische Probe, sondern der mittlere Wert der +// letzten fuenf (Median). Eine oder zwei verirrte Proben verschwinden damit vollstaendig, +// waehrend echte Temperaturaenderungen unverfaelscht durchkommen - nur eben zwei Sekunden +// spaeter. Genau diese kleine Verzoegerung ist der Preis. +// +// Ein erster Anlauf hielt stattdessen beim Standby-Wechsel den zuletzt gemeldeten Wert +// fest. Das machte es schlimmer: Beim Umschalten des Merkers lag die gestoerte Probe +// bereits an, gehalten wurde also ausgerechnet die Delle - und zwar sekundenlang. +// Die angezeigten Zahlen bleiben unangetastet - was gemessen wird, steht dort weiter drin. +#define KURVEN_MEDIAN 5 +static float g_medW[KURVEN_MEDIAN]; +static float g_medD[KURVEN_MEDIAN]; +static uint8_t g_medPos = 0; +static uint8_t g_medFill = 0; + +// Mittlerer Wert von hoechstens fuenf Proben. Bei so wenigen Werten ist Sortieren durch +// Einfuegen das Einfachste, was die Aufgabe loest. +static float median_von(const float* quelle, uint8_t anzahl) { + float f[KURVEN_MEDIAN]; + for (uint8_t i = 0; i < anzahl; i++) { + float v = quelle[i]; + int8_t j = (int8_t)i - 1; + while (j >= 0 && f[j] > v) { f[j + 1] = f[j]; j--; } + f[j + 1] = v; + } + return f[anzahl / 2]; +} static void spark_scan(lv_obj_t* ch, lv_chart_series_t* ser, int32_t* mn, int32_t* mx) { if (!ch || !ser) return; @@ -1970,11 +1993,13 @@ static void chart_sample_cb(lv_timer_t*) { // // 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; + // Frische Probe einreihen, in die Kurven geht der mittlere der letzten fuenf Werte. + g_medW[g_medPos] = g_state.tempW; + g_medD[g_medPos] = g_state.tempD; + g_medPos = (uint8_t)((g_medPos + 1) % KURVEN_MEDIAN); + if (g_medFill < KURVEN_MEDIAN) g_medFill++; + 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)); @@ -4275,16 +4300,6 @@ void ui_update(const MachineState& st) { // Standby-Uhr-Overlay (Backlight wird in onState gesteuert). Uebergaenge werden weich // gefadet: Einschlafen blendet die Uhr ein, Aufwecken blendet sie aus. Nur bei echtem // 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) { bool shouldShow = st.standbyActive && st.standbyShowClock; if (shouldShow) {