|
|
|
@@ -178,14 +178,20 @@ 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 10 // groesster Durchmesser des Punktes
|
|
|
|
|
#define SPARK_DOT_MIN 7 // kleinster - dazwischen atmet er
|
|
|
|
|
#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;
|
|
|
|
@@ -193,6 +199,8 @@ static lv_chart_series_t* g_sparkSerW = nullptr;
|
|
|
|
|
static lv_chart_series_t* g_sparkSerD = nullptr;
|
|
|
|
|
static lv_obj_t* g_sparkDotW = nullptr; // pulsierender Punkt am juengsten Messwert
|
|
|
|
|
static lv_obj_t* g_sparkDotD = nullptr;
|
|
|
|
|
static int32_t g_dotCxW = 0, g_dotCyW = 0; // Mitte des Punktes in der Kurvenflaeche
|
|
|
|
|
static int32_t g_dotCxD = 0, g_dotCyD = 0;
|
|
|
|
|
static lv_obj_t* g_readyBand = nullptr; // Zustandsband der Darstellung TD_BAND
|
|
|
|
|
static lv_obj_t* g_readyBandLbl = nullptr;
|
|
|
|
|
static lv_obj_t* g_tempRow = nullptr; // Container; Inhalt wird je Design (neu) aufgebaut
|
|
|
|
@@ -1196,6 +1204,8 @@ static bool spark_anker(lv_obj_t* ch, lv_chart_series_t* ser, int32_t* anker, in
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void spark_dot_apply(lv_obj_t* d, int32_t groesse); // weiter unten
|
|
|
|
|
|
|
|
|
|
// Punkt am juengsten Messwert an seine Stelle setzen (rechter Rand, Hoehe nach Wert).
|
|
|
|
|
static void spark_dot_place(lv_obj_t* ch, lv_chart_series_t* ser, lv_obj_t* dot,
|
|
|
|
|
int32_t mn, int32_t mx) {
|
|
|
|
@@ -1210,14 +1220,24 @@ static void spark_dot_place(lv_obj_t* ch, lv_chart_series_t* ser, lv_obj_t* dot,
|
|
|
|
|
int32_t w = lv_obj_get_content_width(ch);
|
|
|
|
|
int32_t h = lv_obj_get_content_height(ch);
|
|
|
|
|
if (w <= 0 || h <= 0) return;
|
|
|
|
|
// Die Mitte des Punktes sitzt genau auf dem letzten Messwert, also am rechten Rand der
|
|
|
|
|
// Kurvenflaeche. Damit seine rechte Haelfte trotzdem nicht abgeschnitten wird, laesst
|
|
|
|
|
// das Diagramm rechts einen Rand frei (siehe tempSpark) - dort steht der Punkt, ohne
|
|
|
|
|
// dass die Linie unter ihm hervorschaut.
|
|
|
|
|
int32_t r = SPARK_DOT_SIZE / 2;
|
|
|
|
|
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
|
|
|
|
|
if (dot == g_sparkDotW) { g_dotCxW = w; g_dotCyW = y; }
|
|
|
|
|
else { g_dotCxD = w; g_dotCyD = y; }
|
|
|
|
|
spark_dot_apply(dot, lv_obj_get_width(dot));
|
|
|
|
|
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);
|
|
|
|
@@ -1251,11 +1271,24 @@ 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) {
|
|
|
|
|
// Punkt in seiner aktuellen Groesse an seine Stelle setzen. Die Mitte ist gemerkt, denn
|
|
|
|
|
// beim Atmen aendert sich die Groesse - ohne Nachfuehren wanderte der Punkt davon.
|
|
|
|
|
static void spark_dot_apply(lv_obj_t* d, int32_t groesse) {
|
|
|
|
|
if (!d) return;
|
|
|
|
|
const int32_t cx = (d == g_sparkDotW) ? g_dotCxW : g_dotCxD;
|
|
|
|
|
const int32_t cy = (d == g_sparkDotW) ? g_dotCyW : g_dotCyD;
|
|
|
|
|
lv_obj_set_size(d, groesse, groesse);
|
|
|
|
|
lv_obj_align(d, LV_ALIGN_TOP_LEFT, cx - groesse / 2, cy - groesse / 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Signalpunkt: Der Punkt selbst atmet leicht, und der Schein macht genau das Gegenteil.
|
|
|
|
|
// Zieht der Punkt sich zusammen, geht der Schein auf; wird der Punkt wieder gross, zieht
|
|
|
|
|
// der Schein sich zurueck. Das wirkt wie ein Signal, das ausgesendet wird.
|
|
|
|
|
static void spark_dot_puls_cb(void* var, int32_t v) { // v laeuft 0 .. 100
|
|
|
|
|
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
|
|
|
|
|
spark_dot_apply(d, SPARK_DOT_MIN + ((SPARK_DOT_SIZE - SPARK_DOT_MIN) * v) / 100);
|
|
|
|
|
lv_obj_set_style_shadow_width(d, 30 - (22 * v) / 100, 0); // 30 .. 8
|
|
|
|
|
lv_obj_set_style_shadow_opa(d, (lv_opa_t)(200 - (150 * v) / 100), 0); // 200 .. 50
|
|
|
|
|
}
|
|
|
|
|
static void spark_dot_puls(lv_obj_t* dot) {
|
|
|
|
|
lv_anim_t a; lv_anim_init(&a);
|
|
|
|
@@ -1287,8 +1320,19 @@ static lv_obj_t* tempSpark(lv_obj_t* parent, lv_color_t col, lv_chart_series_t**
|
|
|
|
|
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);
|
|
|
|
|
// Rechts etwas mehr Rand: Dort steht der Punkt auf dem letzten Messwert. Der Platz
|
|
|
|
|
// gehoert noch zum Diagramm, wird aber nicht mehr bezeichnet - deshalb ragt weder die
|
|
|
|
|
// Linie unter dem Punkt hervor noch wird der Punkt an der Kante beschnitten.
|
|
|
|
|
lv_obj_set_style_pad_right(ch, SPARK_DOT_SIZE / 2 + 3, 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 +1345,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, 18, 0);
|
|
|
|
|
lv_obj_set_style_shadow_opa(*dot, 130, 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 +2130,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 +2177,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 +4279,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();
|
|
|
|
|