Merge pull request 'fix(P4): Verlaufskurven mit gemeinsamer Skala - Höhen wieder vergleichbar (1.10.1)' (#83) from fix/verlauf-gemeinsame-skala into main

This commit was merged in pull request #83.
This commit is contained in:
2026-09-03 17:23:29 +02:00
2 changed files with 46 additions and 32 deletions
+9
View File
@@ -1,3 +1,12 @@
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
+37 -32
View File
@@ -1108,26 +1108,38 @@ 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
// eigentliche Frage an eine PID-Regelung, und eine Zahl kann sie grundsaetzlich nicht
// beantworten. Gespeist wird sie aus derselben Sekundenabtastung wie die Verlaufsseite.
// Ausschnitt der Verlaufskurve an die tatsaechlich vorhandenen Werte anpassen.
// Gemeinsamer Ausschnitt fuer BEIDE Verlaufskurven.
//
// Frueher spannte er sich um den SOLLWERT: sechs Grad darunter bis drei darueber. Das
// funktioniert nur, solange der Kessel schon nahe am Ziel ist. Heizt er noch auf oder ist
// er ganz aus, liegt jeder Messwert unterhalb des Fensters und wird auf den unteren Rand
// geklemmt - beide Kurven lagen dann flach auf derselben Hoehe und bewegten sich nicht,
// obwohl der eine Kessel bei 80 Grad stand und der andere bei 0.
static void spark_range_fit(lv_obj_t* ch, lv_chart_series_t* ser) {
// 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;
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);
int32_t mn = INT32_MAX, mx = INT32_MIN;
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];
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) { // ruhige Regelung: Kurve mittig halten
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;
@@ -1138,17 +1150,12 @@ static void spark_range_fit(lv_obj_t* ch, lv_chart_series_t* ser) {
}
// 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.
// Den zuletzt gesetzten Ausschnitt merkt sich die Kurve selbst; abfragen kann man ihn
// bei LVGL nicht.
int32_t* merk = (int32_t*)lv_obj_get_user_data(ch);
if (merk && merk[0] == mn && merk[1] == mx) return;
if (!merk) {
merk = (int32_t*)lv_malloc(2 * sizeof(int32_t));
if (!merk) { lv_chart_set_range(ch, LV_CHART_AXIS_PRIMARY_Y, mn, mx); return; }
lv_obj_set_user_data(ch, merk);
}
merk[0] = mn; merk[1] = mx;
lv_chart_set_range(ch, LV_CHART_AXIS_PRIMARY_Y, mn, mx);
// 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,
@@ -1179,8 +1186,7 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t**
// ersten Sekundenprobe eine leere Flaeche.
(void)soll;
lv_chart_set_all_value(ch, *ser, (int32_t)(jetzt * 10.0f));
spark_range_fit(ch, *ser);
return ch;
return ch; // Ausschnitt setzt spark_ranges_fit(), sobald beide Kurven stehen
}
// Temperaturbereich gemaess aktiver Darstellung (neu) aufbauen - auch beim Umschalten
@@ -1193,6 +1199,7 @@ static void build_temp_row_content() {
bulbTempW = bulbTempD = nullptr;
g_sparkW = g_sparkD = nullptr;
g_sparkSerW = g_sparkSerD = nullptr;
g_sparkMin = g_sparkMax = INT32_MIN; // gemerkter Ausschnitt gilt fuer die alten Kurven
g_readyBand = g_readyBandLbl = nullptr;
const lv_color_t teal = lv_color_hex(0x80cbc4);
@@ -1221,6 +1228,7 @@ static void build_temp_row_content() {
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);
spark_ranges_fit();
break;
}
@@ -1720,16 +1728,13 @@ static void chart_duty_btn_cb(lv_event_t*) {
}
chart_redraw();
// Verlaufskurven des Dashboards (Darstellung "Verlauf") mitfuehren. Der Ausschnitt
// richtet sich nach den vorhandenen Werten, siehe spark_range_fit().
if (g_sparkSerW && g_sparkW) {
// Verlaufskurven des Dashboards (Darstellung "Verlauf") mitfuehren. Beide teilen sich
// eine Skala, siehe spark_ranges_fit().
if (g_sparkSerW && g_sparkW)
lv_chart_set_next_value(g_sparkW, g_sparkSerW, (int32_t)(g_state.tempW * 10.0f));
spark_range_fit(g_sparkW, g_sparkSerW);
}
if (g_sparkSerD && g_sparkD) {
if (g_sparkSerD && g_sparkD)
lv_chart_set_next_value(g_sparkD, g_sparkSerD, (int32_t)(g_state.tempD * 10.0f));
spark_range_fit(g_sparkD, g_sparkSerD);
}
spark_ranges_fit(); // gemeinsame Skala, damit die Hoehen vergleichbar bleiben
}
static void build_chart(lv_obj_t* p) {
lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE);