Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5ea697f24 | ||
|
|
5c64892c01 | ||
|
|
1febc0b092 | ||
|
|
a84f902f75 | ||
|
|
bcbeea2a17 | ||
|
|
1fe96b70aa | ||
|
|
43517a2c48 | ||
|
|
6a867e54fd | ||
|
|
edfa2d4711 | ||
|
|
fc0a756127 | ||
|
|
cc9ae945b1 | ||
|
|
696707a9a4 | ||
|
|
6bf046801c | ||
|
|
cd1617fdce | ||
|
|
5df6c20171 | ||
|
|
7d7893cf08 | ||
|
|
db707ffd8c | ||
|
|
9eb386095d |
@@ -1,3 +1,109 @@
|
|||||||
|
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
|
||||||
|
Verlauf sieht dadurch bei jeder Kesseltemperatur gleich aus.
|
||||||
|
- Beide Kurven behalten denselben Maßstab. Die absolute Höhe lässt sich damit nicht mehr
|
||||||
|
zwischen Wasser und Dampf vergleichen - der Ausschlag aber schon: Wer weiter ausschlägt,
|
||||||
|
hat sich auch stärker verändert.
|
||||||
|
- Am jüngsten Messwert sitzt ein kleiner weißer Punkt mit weichem Schein, der langsam
|
||||||
|
pulsiert.
|
||||||
|
- Beide Linien sind jetzt weiß. Die Farbe der Karte steckt ohnehin in der Überschrift.
|
||||||
|
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
- 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
|
||||||
|
mehrere Sekunden dunkel. Das war die ganze gesuchte Verzögerung.
|
||||||
|
- Ursache: Die Bridge übernimmt eine neue Helligkeit nicht zuverlässig beim ersten Mal. Der
|
||||||
|
Beispielcode von Waveshare setzt sie deshalb zweimal, mit einer Pause dazwischen,
|
||||||
|
ausdrücklich „sicherheitshalber". Genau diese Wiederholung fehlte.
|
||||||
|
- Die Panel-Freigabe wird nur noch beim ersten Mal gesetzt. Sie erneut zu setzen, während die
|
||||||
|
Beleuchtung läuft, stößt die Helligkeitsstufe der Bridge neu an; der Beispielcode setzt sie
|
||||||
|
ebenfalls nur ein einziges Mal.
|
||||||
|
- Jeder Helligkeitswechsel meldet jetzt seine Dauer. Bleibt die Beleuchtung wieder hinterher,
|
||||||
|
lässt sich die Pause zwischen den beiden Übernahmen über WS7_BACKLIGHT_LATCH_MS erhöhen.
|
||||||
|
|
||||||
|
Version 1.9.11:
|
||||||
|
- Endlose Startschleife behoben: Beleuchtung an, nie ein Bild. Der P4 startet sich nach dem
|
||||||
|
Einschalten absichtlich einmal neu, weil das Panel sonst schwarz bleibt. Die Absicherung
|
||||||
|
dagegen verließ sich darauf, dass der zweite Start kein Kaltstart mehr ist - der Chip
|
||||||
|
meldet aber auch nach dem selbst ausgelösten Neustart wieder einen Kaltstart. Damit griff
|
||||||
|
die Bedingung erneut, endlos.
|
||||||
|
- Der Neustart wird jetzt vermerkt und beim zweiten Durchlauf erkannt, unabhängig davon, was
|
||||||
|
der Chip als Startgrund meldet. Beim nächsten echten Einschalten erfolgt wieder genau ein
|
||||||
|
Neustart.
|
||||||
|
- Die Meldung über diesen Neustart lief bisher über die USB-Verbindung und war im seriellen
|
||||||
|
Monitor nicht zu sehen - deshalb war von der Schleife nichts zu erkennen. Sie läuft jetzt
|
||||||
|
denselben Weg wie die übrigen Panel-Meldungen.
|
||||||
|
|
||||||
|
Version 1.9.10:
|
||||||
|
- Alle Messzeilen laufen jetzt auf derselben Uhr. Die des Zeichenablaufs startete später als
|
||||||
|
die des Hauptablaufs; die Zeiten ließen sich nur mit Umrechnung vergleichen.
|
||||||
|
- Der Touch meldet jetzt, wenn er Rohwerte verwirft, weil sie außerhalb des gültigen
|
||||||
|
Bereichs liegen, und nennt bei jeder erkannten Berührung, wie viele vorher verworfen
|
||||||
|
wurden. Eine Berührung, deren Rohwerte lange verworfen werden, sieht von außen genauso aus
|
||||||
|
wie ein Display, das nicht reagiert - das war bisher nicht zu unterscheiden.
|
||||||
|
|
||||||
Version 1.9.9:
|
Version 1.9.9:
|
||||||
- Die kleinen Kacheln unten im Dashboard behalten beim Bezug ihre Höhe. Bisher bekam die
|
- Die kleinen Kacheln unten im Dashboard behalten beim Bezug ihre Höhe. Bisher bekam die
|
||||||
untere Zeile während des Bezugs deutlich mehr Platz - die Kacheln wurden enorm hoch und
|
untere Zeile während des Bezugs deutlich mehr Platz - die Kacheln wurden enorm hoch und
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -80,27 +67,11 @@ static void onState(const MachineState& st) {
|
|||||||
}
|
}
|
||||||
if (bl < 0) bl = 0; if (bl > 100) bl = 100;
|
if (bl < 0) bl = 0; if (bl > 100) bl = 100;
|
||||||
if (bl != s_lastBacklight) {
|
if (bl != s_lastBacklight) {
|
||||||
// Vermessen: Rund um jeden Helligkeitswechsel hing die Bedienung bisher ein bis
|
hal_backlight(bl); // meldet seine Dauer selbst, siehe jc_backlight_set()
|
||||||
// zwei Sekunden. Der Schreibzugriff geht ueber denselben I2C-Bus wie der Touch.
|
|
||||||
uint32_t t0 = millis();
|
|
||||||
hal_backlight(bl);
|
|
||||||
uint32_t dt = millis() - t0;
|
|
||||||
if (dt > 50) esp_rom_printf("[HAL] %u ms: Helligkeit %d %% gesetzt, brauchte %u ms\n",
|
|
||||||
(unsigned)millis(), bl, (unsigned)dt);
|
|
||||||
s_lastBacklight = bl;
|
s_lastBacklight = bl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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" ||
|
||||||
@@ -336,20 +307,37 @@ void setup() {
|
|||||||
// nicht (siehe WS7_DSI_RESTART).
|
// nicht (siehe WS7_DSI_RESTART).
|
||||||
//
|
//
|
||||||
// Deshalb startet der P4 sich nach einem Kaltstart genau einmal selbst neu, nachdem
|
// Deshalb startet der P4 sich nach einem Kaltstart genau einmal selbst neu, nachdem
|
||||||
// der erste Hochlauf durch ist. Beim zweiten Durchlauf ist der Startgrund kein
|
// der erste Hochlauf durch ist. Kostet rund drei Sekunden, aber nur beim Einschalten.
|
||||||
// Kaltstart mehr, die Bedingung greift also nicht erneut - eine Schleife ist
|
//
|
||||||
// ausgeschlossen. Kostet rund drei Sekunden, aber nur beim Einschalten.
|
// Frueher stuetzte sich die Absicherung dagegen allein auf den Startgrund: Beim zweiten
|
||||||
|
// Durchlauf sei es kein Kaltstart mehr, eine Schleife also ausgeschlossen. Das stimmt
|
||||||
|
// nicht - der Chip meldet auch nach dem selbst ausgeloesten Neustart wieder einen
|
||||||
|
// Kaltstart, und dann startet er endlos neu. Genau das ist aufgetreten: Beleuchtung an,
|
||||||
|
// nie ein Bild. Deshalb wird jetzt zusaetzlich vermerkt, dass der Neustart schon
|
||||||
|
// stattgefunden hat; darauf ist Verlass, unabhaengig vom gemeldeten Startgrund.
|
||||||
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_COLD_BOOT_RESTART
|
#if (JC_PANEL_TYPE == WS_PANEL_7H) && WS7_COLD_BOOT_RESTART
|
||||||
if (esp_reset_reason() == ESP_RST_POWERON) {
|
if (esp_reset_reason() == ESP_RST_POWERON) {
|
||||||
|
Preferences p;
|
||||||
|
p.begin("bootguard", false);
|
||||||
|
bool schonNeugestartet = p.getUChar("kaltneu", 0) != 0;
|
||||||
|
if (schonNeugestartet) {
|
||||||
|
// Zweiter Durchlauf: Vermerk loeschen, damit beim naechsten echten Einschalten
|
||||||
|
// wieder genau ein Neustart erfolgt.
|
||||||
|
p.putUChar("kaltneu", 0);
|
||||||
|
p.end();
|
||||||
|
esp_rom_printf("[Panel] Neustart bereits erfolgt - Hochlauf wird fortgesetzt\n");
|
||||||
|
} else {
|
||||||
|
p.putUChar("kaltneu", 1);
|
||||||
// Der Fruehstart-Zaehler wurde oben bereits erhoeht. Diesen gewollten Neustart
|
// Der Fruehstart-Zaehler wurde oben bereits erhoeht. Diesen gewollten Neustart
|
||||||
// darf er nicht mitzaehlen, sonst laeuft die Absturzsicherung nach ein paar
|
// darf er nicht mitzaehlen, sonst laeuft die Absturzsicherung nach ein paar
|
||||||
// Einschaltvorgaengen in den Update-Modus.
|
// Einschaltvorgaengen in den Update-Modus.
|
||||||
{ Preferences p; p.begin("bootguard", false); p.putUShort("early", 0); p.end(); }
|
p.putUShort("early", 0);
|
||||||
DBG_SERIAL.println(F("[Panel] Kaltstart - einmaliger Neustart fuer das Displaypanel"));
|
p.end();
|
||||||
DBG_SERIAL.flush();
|
esp_rom_printf("[Panel] Kaltstart - einmaliger Neustart fuer das Displaypanel\n");
|
||||||
delay(50);
|
delay(50);
|
||||||
esp_restart();
|
esp_restart();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
g_client.begin(); // UART starten, hello folgt automatisch in loop()
|
g_client.begin(); // UART starten, hello folgt automatisch in loop()
|
||||||
|
|
||||||
@@ -361,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()
|
||||||
|
|
||||||
|
|||||||
@@ -235,9 +235,34 @@ void jc_backlight_set(int percent)
|
|||||||
if (percent > 100) percent = 100;
|
if (percent > 100) percent = 100;
|
||||||
if (percent < 0) percent = 0;
|
if (percent < 0) percent = 0;
|
||||||
uint8_t brightness = (uint8_t)((255 * percent) / 100);
|
uint8_t brightness = (uint8_t)((255 * percent) / 100);
|
||||||
|
uint8_t wert = (uint8_t)(255 - brightness);
|
||||||
|
int64_t t0 = esp_timer_get_time();
|
||||||
|
|
||||||
|
// Die Bridge uebernimmt eine neue Helligkeit nicht zuverlaessig beim ersten Mal. Der
|
||||||
|
// Beispielcode von Waveshare setzt sie deshalb zweimal, mit einer Pause dazwischen,
|
||||||
|
// ausdruecklich "sicherheitshalber". Uns fehlte diese Wiederholung: Das Bild stand
|
||||||
|
// dann fertig da, blieb aber dunkel, bis die Uebernahme irgendwann doch griff - beim
|
||||||
|
// Ein- und Ausschalten des Standby waren das mehrere Sekunden.
|
||||||
|
//
|
||||||
|
// Die Freigabe (0xAD) bleibt beim ersten Aufruf, danach nicht mehr: Sie erneut zu
|
||||||
|
// setzen, waehrend die Beleuchtung laeuft, stoesst die Helligkeitsstufe der Bridge neu
|
||||||
|
// an - der Beispielcode setzt sie darum ebenfalls nur ein einziges Mal.
|
||||||
|
static bool freigegeben = false;
|
||||||
|
if (!freigegeben) {
|
||||||
ws7_bridge_write(0xAD, 0x01); // Panel-/Backlight-Freigabe
|
ws7_bridge_write(0xAD, 0x01); // Panel-/Backlight-Freigabe
|
||||||
ws7_bridge_write(0xAB, (uint8_t)(255 - brightness));
|
vTaskDelay(pdMS_TO_TICKS(20)); // wie im Beispielcode: Bridge setzen lassen
|
||||||
|
freigegeben = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws7_bridge_write(0xAB, wert);
|
||||||
ws7_bridge_write(0xAA, 0x01); // Helligkeit uebernehmen
|
ws7_bridge_write(0xAA, 0x01); // Helligkeit uebernehmen
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(WS7_BACKLIGHT_LATCH_MS));
|
||||||
|
ws7_bridge_write(0xAB, wert); // Wiederholung, siehe oben
|
||||||
|
ws7_bridge_write(0xAA, 0x01);
|
||||||
|
|
||||||
|
esp_rom_printf("[HAL] %u ms: Helligkeit %d %% gesetzt, brauchte %u ms\n",
|
||||||
|
(unsigned)(esp_timer_get_time() / 1000), percent,
|
||||||
|
(unsigned)((esp_timer_get_time() - t0) / 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
@@ -310,14 +335,26 @@ static bool ws7_touch_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y
|
|||||||
bool got = s_touch_get_xy_orig(tp, x, y, strength, point_num, max_point_num);
|
bool got = s_touch_get_xy_orig(tp, x, y, strength, point_num, max_point_num);
|
||||||
int64_t now = esp_timer_get_time();
|
int64_t now = esp_timer_get_time();
|
||||||
|
|
||||||
// Unplausible Rohwerte gelten als Aussetzer, nicht als Beruehrung.
|
// Unplausible Rohwerte gelten als Aussetzer, nicht als Beruehrung. Wie viele davon
|
||||||
|
// anfallen, war bisher nirgends zu sehen - eine Beruehrung, deren Rohwerte lange
|
||||||
|
// verworfen werden, sieht von aussen wie ein Display aus, das nicht reagiert.
|
||||||
|
static uint32_t s_verworfen = 0;
|
||||||
if (got && *point_num > 0 &&
|
if (got && *point_num > 0 &&
|
||||||
(x[0] > WS7_TOUCH_RAW_X_MAX || y[0] > WS7_TOUCH_RAW_Y_MAX)) {
|
(x[0] > WS7_TOUCH_RAW_X_MAX || y[0] > WS7_TOUCH_RAW_Y_MAX)) {
|
||||||
|
if (s_verworfen == 0)
|
||||||
|
esp_rom_printf("[Touch] %u ms: Rohwert verworfen (x %u, y %u liegen ausserhalb)\n",
|
||||||
|
(unsigned)(now / 1000), (unsigned)x[0], (unsigned)y[0]);
|
||||||
|
s_verworfen++;
|
||||||
got = false;
|
got = false;
|
||||||
*point_num = 0;
|
*point_num = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (got && *point_num > 0) {
|
if (got && *point_num > 0) {
|
||||||
|
if (!s_touch_down)
|
||||||
|
esp_rom_printf("[Touch] %u ms: Beruehrung erkannt bei x %u, y %u; %u Rohwerte vorher verworfen\n",
|
||||||
|
(unsigned)(now / 1000), (unsigned)x[0], (unsigned)y[0],
|
||||||
|
(unsigned)s_verworfen);
|
||||||
|
s_verworfen = 0;
|
||||||
s_touch_last_x = x[0];
|
s_touch_last_x = x[0];
|
||||||
s_touch_last_y = y[0];
|
s_touch_last_y = y[0];
|
||||||
s_touch_last_us = now;
|
s_touch_last_us = now;
|
||||||
|
|||||||
@@ -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.11.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
// Protokoll
|
// Protokoll
|
||||||
|
|||||||
@@ -182,6 +182,14 @@
|
|||||||
// auf I2C antwortet. Meldet sich 0x14 beim Start nicht, diesen Wert erhoehen.
|
// auf I2C antwortet. Meldet sich 0x14 beim Start nicht, diesen Wert erhoehen.
|
||||||
#define WS7_TOUCH_POWER_MS 120
|
#define WS7_TOUCH_POWER_MS 120
|
||||||
|
|
||||||
|
// Pause zwischen den beiden Helligkeitsuebernahmen der Bridge. Sie uebernimmt eine neue
|
||||||
|
// Helligkeit nicht zuverlaessig beim ersten Mal; der Beispielcode von Waveshare setzt sie
|
||||||
|
// darum zweimal. Ohne die Wiederholung stand das Bild fertig da, blieb aber dunkel - beim
|
||||||
|
// Wechsel in den und aus dem Standby mehrere Sekunden lang. Der Beispielcode wartet 100 ms;
|
||||||
|
// weniger genuegt hier, weil die Wiederholung ohnehin folgt. Bleibt die Beleuchtung wieder
|
||||||
|
// hinterher, diesen Wert erhoehen.
|
||||||
|
#define WS7_BACKLIGHT_LATCH_MS 20
|
||||||
|
|
||||||
// Prueffarbbalken statt Oberflaeche anzeigen. Sie entstehen im DSI-Baustein selbst und
|
// Prueffarbbalken statt Oberflaeche anzeigen. Sie entstehen im DSI-Baustein selbst und
|
||||||
// benutzen weder Bildspeicher noch LVGL. Damit laesst sich trennen, ob die Anzeigekette
|
// benutzen weder Bildspeicher noch LVGL. Damit laesst sich trennen, ob die Anzeigekette
|
||||||
// bis zum Panel arbeitet (Balken sichtbar) oder nicht (schwarz). Nur zur Fehlersuche.
|
// bis zum Panel arbeitet (Balken sichtbar) oder nicht (schwarz). Nur zur Fehlersuche.
|
||||||
|
|||||||
+237
-54
@@ -179,10 +179,20 @@ 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
|
||||||
|
// 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_PULS_MS 1100
|
||||||
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;
|
||||||
static lv_chart_series_t* g_sparkSerD = 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 lv_obj_t* g_readyBand = nullptr; // Zustandsband der Darstellung TD_BAND
|
static lv_obj_t* g_readyBand = nullptr; // Zustandsband der Darstellung TD_BAND
|
||||||
static lv_obj_t* g_readyBandLbl = nullptr;
|
static lv_obj_t* g_readyBandLbl = nullptr;
|
||||||
static lv_obj_t* g_tempRow = nullptr; // Container; Inhalt wird je Design (neu) aufgebaut
|
static lv_obj_t* g_tempRow = nullptr; // Container; Inhalt wird je Design (neu) aufgebaut
|
||||||
@@ -719,13 +729,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 +938,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 = lv_tick_get();
|
|
||||||
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)lv_tick_get(), (unsigned)(lv_tick_get() - 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 +1115,170 @@ 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_sparkMinW = INT32_MIN; // zuletzt gesetzter Ausschnitt je Kurve
|
||||||
|
static int32_t g_sparkMaxW = INT32_MIN;
|
||||||
|
static int32_t g_sparkMinD = INT32_MIN;
|
||||||
|
static int32_t g_sparkMaxD = 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, 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ausschnitt einer Kurve: Der aelteste Punkt - links am Rand - liegt genau auf halber
|
||||||
|
// Hoehe, und die Kurve waechst von dort nach oben oder unten. Das ist der ruhige Eindruck,
|
||||||
|
// den ein Verlauf haben soll: Er beginnt immer an derselben Stelle, egal bei welcher
|
||||||
|
// Temperatur der Kessel gerade steht.
|
||||||
|
//
|
||||||
|
// Beide Kurven bekommen denselben Massstab (gleiche Grad je Bildpunkt). Die absolute Hoehe
|
||||||
|
// laesst sich damit zwar nicht mehr vergleichen - dafuer aber der Ausschlag: Eine Kurve,
|
||||||
|
// die doppelt so weit ausschlaegt, hat sich auch doppelt so stark veraendert.
|
||||||
|
//
|
||||||
|
// Rueckgabe: falsch, wenn die Kurve noch keinen brauchbaren Wert hat.
|
||||||
|
static bool spark_anker(lv_obj_t* ch, lv_chart_series_t* ser, int32_t* anker, int32_t* abweichung) {
|
||||||
|
if (!ch || !ser) return false;
|
||||||
|
int32_t* pts = lv_chart_get_y_array(ch, ser);
|
||||||
|
if (!pts) return false;
|
||||||
|
uint32_t n = lv_chart_get_point_count(ch);
|
||||||
|
if (n == 0) return false;
|
||||||
|
uint32_t erster = lv_chart_get_x_start_point(ch, ser) % n; // aeltester Punkt im Ring
|
||||||
|
int32_t a = pts[erster];
|
||||||
|
if (a == LV_CHART_POINT_NONE) return false;
|
||||||
|
int32_t weit = 0;
|
||||||
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
|
if (pts[i] == LV_CHART_POINT_NONE) continue;
|
||||||
|
int32_t d = pts[i] - a;
|
||||||
|
if (d < 0) d = -d;
|
||||||
|
if (d > weit) weit = d;
|
||||||
|
}
|
||||||
|
*anker = a;
|
||||||
|
*abweichung = weit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if (!ch || !ser || !dot) return;
|
||||||
|
if (mx <= mn) return;
|
||||||
|
int32_t* pts = lv_chart_get_y_array(ch, ser);
|
||||||
|
uint32_t n = lv_chart_get_point_count(ch);
|
||||||
|
if (!pts || n == 0) return;
|
||||||
|
uint32_t letzter = (lv_chart_get_x_start_point(ch, ser) + n - 1) % n; // juengster Punkt
|
||||||
|
int32_t v = pts[letzter];
|
||||||
|
if (v == LV_CHART_POINT_NONE) { lv_obj_add_flag(dot, LV_OBJ_FLAG_HIDDEN); return; }
|
||||||
|
int32_t w = lv_obj_get_content_width(ch);
|
||||||
|
int32_t h = lv_obj_get_content_height(ch);
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
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
|
||||||
|
lv_obj_remove_flag(dot, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
bool okD = spark_anker(g_sparkD, g_sparkSerD, &aD, &dD);
|
||||||
|
if (!okW && !okD) return; // noch kein einziger Wert
|
||||||
|
|
||||||
|
int32_t halb = (okW && dW > dD) ? dW : dD; // gemeinsamer Massstab: der groessere Ausschlag
|
||||||
|
if (!okW) halb = dD;
|
||||||
|
if (!okD) halb = dW;
|
||||||
|
halb += halb / 8; // etwas Luft, damit die Kurve nicht den Rand kuesst
|
||||||
|
if (halb < DASH_SPARK_MIN_SPAN / 2) halb = DASH_SPARK_MIN_SPAN / 2;
|
||||||
|
|
||||||
|
// 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 (okW) {
|
||||||
|
int32_t mn = aW - halb, mx = aW + halb;
|
||||||
|
if (mn != g_sparkMinW || mx != g_sparkMaxW) {
|
||||||
|
g_sparkMinW = mn; g_sparkMaxW = mx;
|
||||||
|
lv_chart_set_range(g_sparkW, LV_CHART_AXIS_PRIMARY_Y, mn, mx);
|
||||||
|
}
|
||||||
|
spark_dot_place(g_sparkW, g_sparkSerW, g_sparkDotW, g_sparkMinW, g_sparkMaxW);
|
||||||
|
}
|
||||||
|
if (okD) {
|
||||||
|
int32_t mn = aD - halb, mx = aD + halb;
|
||||||
|
if (mn != g_sparkMinD || mx != g_sparkMaxD) {
|
||||||
|
g_sparkMinD = mn; g_sparkMaxD = mx;
|
||||||
|
lv_chart_set_range(g_sparkD, LV_CHART_AXIS_PRIMARY_Y, mn, mx);
|
||||||
|
}
|
||||||
|
spark_dot_place(g_sparkD, g_sparkSerD, g_sparkDotD, g_sparkMinD, g_sparkMaxD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
static void spark_dot_puls(lv_obj_t* dot) {
|
||||||
|
lv_anim_t a; lv_anim_init(&a);
|
||||||
|
lv_anim_set_var(&a, dot);
|
||||||
|
lv_anim_set_exec_cb(&a, spark_dot_puls_cb);
|
||||||
|
lv_anim_set_values(&a, 0, 100);
|
||||||
|
lv_anim_set_duration(&a, SPARK_DOT_PULS_MS);
|
||||||
|
lv_anim_set_playback_duration(&a, SPARK_DOT_PULS_MS);
|
||||||
|
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
||||||
|
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
|
||||||
|
lv_anim_start(&a);
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
lv_obj_t** dot, 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 +1292,30 @@ 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
|
// Punkt am juengsten Messwert, mit weichem Schein. Er haengt als eigenes Element im
|
||||||
// "kein Wert"-Punkte und zeichnet deshalb nichts - beim Umschalten der Darstellung
|
// Diagramm; gesetzt wird seine Stelle in spark_dot_place, sobald der Ausschnitt steht.
|
||||||
// saehe man bis zur ersten Sekundenprobe eine leere Flaeche.
|
*dot = lv_obj_create(ch);
|
||||||
int32_t sollT = (int32_t)(soll * 10.0f);
|
lv_obj_remove_style_all(*dot);
|
||||||
lv_chart_set_range(ch, LV_CHART_AXIS_PRIMARY_Y, sollT - 60, sollT + 30);
|
lv_obj_set_size(*dot, SPARK_DOT_SIZE, SPARK_DOT_SIZE);
|
||||||
|
lv_obj_set_style_radius(*dot, LV_RADIUS_CIRCLE, 0);
|
||||||
|
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_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
|
||||||
|
// in der linken oberen Ecke auf.
|
||||||
|
lv_obj_add_flag(*dot, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
spark_dot_puls(*dot);
|
||||||
|
|
||||||
|
// Startwert sofort setzen. Ein frisch angelegtes Diagramm hat lauter "kein Wert"-Punkte
|
||||||
|
// und zeichnet deshalb nichts - beim Umschalten der Darstellung saehe man bis zur
|
||||||
|
// ersten Sekundenprobe eine leere Flaeche.
|
||||||
|
(void)soll;
|
||||||
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 +1328,9 @@ 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_sparkDotW = g_sparkDotD = nullptr;
|
||||||
|
g_sparkMinW = g_sparkMaxW = INT32_MIN; // gemerkter Ausschnitt gilt fuer die alten Kurven
|
||||||
|
g_sparkMinD = g_sparkMaxD = INT32_MIN;
|
||||||
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);
|
||||||
@@ -1173,10 +1355,15 @@ static void build_temp_row_content() {
|
|||||||
case TD_VERLAUF: {
|
case TD_VERLAUF: {
|
||||||
lv_obj_t* cw = tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
lv_obj_t* cw = tempField(g_tempRow, "WASSER", COL_ACCENT, &lblTempW, &lblSetW, &barDutyW,
|
||||||
1, &lv_font_maven_pro_bold_96, false, 0);
|
1, &lv_font_maven_pro_bold_96, false, 0);
|
||||||
g_sparkW = tempSpark(cw, COL_OK, &g_sparkSerW, g_state.tempW, g_state.setW);
|
// Beide Linien weiss: Die Farbe der Karte steckt schon in der Ueberschrift, und
|
||||||
|
// zwei verschiedenfarbige Linien nebeneinander wirken unruhiger als eine Sprache.
|
||||||
|
g_sparkW = tempSpark(cw, lv_color_white(), &g_sparkSerW, &g_sparkDotW,
|
||||||
|
g_state.tempW, g_state.setW);
|
||||||
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, lv_color_white(), &g_sparkSerD, &g_sparkDotD,
|
||||||
|
g_state.tempD, g_state.setD);
|
||||||
|
spark_ranges_fit();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,20 +1862,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 +2086,33 @@ 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.
|
||||||
|
// 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));
|
||||||
|
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)));
|
||||||
@@ -3563,7 +3758,7 @@ static void refr_ready_cb(lv_event_t*) {
|
|||||||
uint32_t d = lv_tick_get() - g_refrStart;
|
uint32_t d = lv_tick_get() - g_refrStart;
|
||||||
if (d < REFR_WARN_MS) return;
|
if (d < REFR_WARN_MS) return;
|
||||||
unsigned pct = (unsigned)((uint64_t)g_refrPixels * 100ULL / ((uint64_t)LCD_H_RES * LCD_V_RES));
|
unsigned pct = (unsigned)((uint64_t)g_refrPixels * 100ULL / ((uint64_t)LCD_H_RES * LCD_V_RES));
|
||||||
esp_rom_printf("[UI] %u ms: Bildaufbau %u ms, %u Bereiche, %u %% der Flaeche\n", (unsigned)lv_tick_get(),
|
esp_rom_printf("[UI] %u ms: Bildaufbau %u ms, %u Bereiche, %u %% der Flaeche\n", (unsigned)millis(),
|
||||||
(unsigned)d, (unsigned)g_refrAreas, pct);
|
(unsigned)d, (unsigned)g_refrAreas, pct);
|
||||||
// Lage der Bereiche mit ausgeben. Erst daran ist zu erkennen, WELCHER Teil der
|
// Lage der Bereiche mit ausgeben. Erst daran ist zu erkennen, WELCHER Teil der
|
||||||
// Oberflaeche das Neuzeichnen ausloest - die Groesse allein sagt das nicht.
|
// Oberflaeche das Neuzeichnen ausloest - die Groesse allein sagt das nicht.
|
||||||
@@ -3573,13 +3768,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)lv_tick_get(), (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 +4212,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4217,14 +4406,8 @@ void ui_update(const MachineState& st) {
|
|||||||
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 = lv_tick_get();
|
|
||||||
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)(lv_tick_get() - 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 -
|
||||||
|
|||||||
Reference in New Issue
Block a user