Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Opus 5 cc9ae945b1 fix(P4): Verlaufskurven skalieren nach Daten, Diagnose ausgebaut (1.10.0)
Die Sparklines im Dashboard lagen flach auf gleicher Höhe und bewegten sich
nicht, obwohl ein Kessel bei 80 °C stand und der andere bei 0.

Ursache: lv_chart_set_range() war an den Sollwert gebunden (soll-60 bis
soll+30 in Zehntelgrad). Liegt die Temperatur unter diesem Fenster - beim
Aufheizen oder bei abgeschaltetem Kessel -, klemmt LVGL jeden Punkt auf den
unteren Rand. Beide Kurven landeten dadurch auf derselben Linie.

- Neues spark_range_fit(): Ausschnitt aus min/max der vorhandenen Punkte
  (lv_chart_get_y_array), Mindestspanne DASH_SPARK_MIN_SPAN (40 = 4,0 °C),
  sonst ein Achtel Luft nach oben und unten.
- Der zuletzt gesetzte Bereich wird in user_data gemerkt, weil LVGL keinen
  Getter dafür hat; ohne den Vergleich würde set_range das Diagramm bei jeder
  Zustandsmeldung invalidieren.
- Die Charts füllen jetzt den Platz unterhalb der Zahlen (flex_grow 1,
  min_height 90) statt als 64-px-Streifen mittig zu hängen.

Aufräumen nach der abgeschlossenen Fehlersuche:
- Herzschlag, [Zustand]- und [Touch]-Messzeilen entfernt.
- refr_monitor_install() hinter WS7_PERF_MONITOR (aus).
- scroll_ohne_nachlauf() gibt LV_OBJ_FLAG_SCROLL_MOMENTUM wieder frei.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G95njDFkedFqXiz5sEaJ2
2026-09-03 17:01:19 +02:00
thomas 696707a9a4 Merge pull request 'fix(P4): Hintergrundbeleuchtung kommt sofort - Bridge braucht zwei Latches (1.9.12)' (#81) from fix/helligkeit-doppelte-uebernahme into main 2026-09-03 15:46:17 +02:00
raw-designsandClaude Opus 5 6bf046801c fix(P4): Hintergrundbeleuchtung kommt sofort - Bridge braucht zwei Latches (1.9.12)
Der Nutzer hat es im Gegenlicht gesehen: Das Bild steht nach 446 ms fertig
da, nur die Beleuchtung kommt Sekunden später. Die gesamte Suche im
Zeichenablauf lief damit am falschen Ort - die Messungen dort waren alle
unauffällig, weil dort auch nichts war.

Der Referenz-Sketch (dsi-test.ino, enableBacklight) setzt 0xAB/0xAA zweimal
mit delay(100) dazwischen, ausdrücklich "sicherheitshalber Helligkeit noch
einmal übernehmen". jc_backlight_set() schrieb die Sequenz nur einmal.

- 0xAB/0xAA werden jetzt wiederholt, Pause über WS7_BACKLIGHT_LATCH_MS (20).
- 0xAD (Freigabe) nur noch beim ersten Aufruf statt bei jeder Änderung; der
  Referenzcode setzt sie ebenfalls nur einmal, und sie erneut zu schreiben
  stößt die Helligkeitsstufe der Bridge neu an.
- jc_backlight_set() meldet seine Dauer selbst; die doppelte Messung im .ino
  entfällt.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 15:45:50 +02:00
thomas cd1617fdce Merge pull request 'fix(P4): Endlose Neustartschleife nach Kaltstart (1.9.11)' (#80) from fix/kaltstart-neustartschleife into main 2026-09-03 15:09:03 +02:00
raw-designsandClaude Opus 5 5df6c20171 fix(P4): Endlose Neustartschleife nach Kaltstart (1.9.11)
Log zeigt zweimal hintereinander "rst:0x1 (POWERON)" und
"[Panel] Startgrund: 1", danach nichts mehr - Beleuchtung an, kein Bild.

Ursache: Der gewollte einmalige Neustart nach Kaltstart (WS7_COLD_BOOT_RESTART)
prüfte nur esp_reset_reason() == ESP_RST_POWERON. Der Kommentar nahm an, der
zweite Durchlauf melde einen Software-Reset. Tatsächlich meldet der Chip auch
nach esp_restart() wieder POWERON, die Bedingung greift erneut - Endlosschleife.
Der bootguard konnte nicht eingreifen, weil derselbe Zweig "early" jedes Mal
auf 0 zurücksetzt.

- Zusätzliches NVS-Flag "kaltneu" in bootguard: wird vor esp_restart() gesetzt
  und beim zweiten Durchlauf erkannt und gelöscht. Unabhängig vom gemeldeten
  Startgrund, damit die Schleife strukturell ausgeschlossen ist.
- Die Meldung lief über DBG_SERIAL (USB-CDC) und war im Monitor an der UART
  nicht zu sehen - jetzt esp_rom_printf wie die übrigen Panel-Meldungen.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeFMr439WcLMdebE3kU7fS
2026-09-03 15:08:40 +02:00
thomas 7d7893cf08 Merge pull request 'diag(P4): Touch-Rohwerte vermessen, einheitliche Zeitbasis (1.9.10)' (#79) from diag/touch-rohwerte-und-zeitbasis into main 2026-09-03 13:52:10 +02:00
raw-designsandClaude Opus 5 db707ffd8c diag(P4): Touch-Rohwerte vermessen, einheitliche Zeitbasis (1.9.10)
Auswertung des letzten Logs (lv_tick lief 2728 ms hinter millis):
Tipp erkannt 49428, Rückfrage aufgebaut 49430, Bildaufbau fertig 49746.
Also 318 ms vom erkannten Tipp bis zum fertigen Bild - die gemessene Kette
ist schnell. Der Knopfdruck im Dialog liegt bei 55638 (Bereich 645..856,
374..426), die Antwort des S3 bei 59303, also 937 ms Rundlauf.

Die Verzögerung liegt damit VOR der Erkennung. Deshalb:
- ws7_touch_get_xy() meldet den ersten verworfenen Rohwert nach Ruhe mit
  Koordinaten und zählt, wie viele bis zur akzeptierten Berührung anfallen.
- Alle Messzeilen auf millis() statt lv_tick_get(); die beiden Uhren
  auseinanderzurechnen hat das Lesen unnötig erschwert.

Beide Panel-Varianten mit arduino-cli gegengebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeFMr439WcLMdebE3kU7fS
2026-09-03 13:51:48 +02:00
thomas 9eb386095d Merge pull request 'fix(P4): Kachelhöhe beim Bezug halten, Messzeilen mit Zeitmarke (1.9.9)' (#78) from fix/kachelhoehe-und-zeitmarken into main 2026-09-03 13:38:36 +02:00
5 changed files with 197 additions and 85 deletions
+51
View File
@@ -1,3 +1,54 @@
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
+29 -42
View File
@@ -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,19 +307,36 @@ 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) {
// Der Fruehstart-Zaehler wurde oben bereits erhoeht. Diesen gewollten Neustart Preferences p;
// darf er nicht mitzaehlen, sonst laeuft die Absturzsicherung nach ein paar p.begin("bootguard", false);
// Einschaltvorgaengen in den Update-Modus. bool schonNeugestartet = p.getUChar("kaltneu", 0) != 0;
{ Preferences p; p.begin("bootguard", false); p.putUShort("early", 0); p.end(); } if (schonNeugestartet) {
DBG_SERIAL.println(F("[Panel] Kaltstart - einmaliger Neustart fuer das Displaypanel")); // Zweiter Durchlauf: Vermerk loeschen, damit beim naechsten echten Einschalten
DBG_SERIAL.flush(); // wieder genau ein Neustart erfolgt.
delay(50); p.putUChar("kaltneu", 0);
esp_restart(); 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
// darf er nicht mitzaehlen, sonst laeuft die Absturzsicherung nach ein paar
// Einschaltvorgaengen in den Update-Modus.
p.putUShort("early", 0);
p.end();
esp_rom_printf("[Panel] Kaltstart - einmaliger Neustart fuer das Displaypanel\n");
delay(50);
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()
+40 -3
View File
@@ -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);
ws7_bridge_write(0xAD, 0x01); // Panel-/Backlight-Freigabe uint8_t wert = (uint8_t)(255 - brightness);
ws7_bridge_write(0xAB, (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
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;
+8
View File
@@ -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.
+69 -40
View File
@@ -179,6 +179,9 @@ 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
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;
@@ -719,13 +722,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 +931,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 +1108,59 @@ 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.
// Ausschnitt der Verlaufskurve an die tatsaechlich vorhandenen Werte anpassen.
//
// 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) {
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 (mn > mx) return; // noch kein einziger Wert
if (mx - mn < DASH_SPARK_MIN_SPAN) { // ruhige Regelung: Kurve mittig halten
int32_t mitte = (mn + mx) / 2;
mn = mitte - DASH_SPARK_MIN_SPAN / 2;
mx = mitte + DASH_SPARK_MIN_SPAN / 2;
} else { // sonst etwas Luft nach oben und unten
int32_t luft = (mx - mn) / 8;
mn -= luft;
mx += luft;
}
// 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);
}
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) { 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,12 +1174,12 @@ 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 // Startwert sofort setzen. Ein frisch angelegtes Diagramm hat lauter "kein Wert"-Punkte
// "kein Wert"-Punkte und zeichnet deshalb nichts - beim Umschalten der Darstellung // und zeichnet deshalb nichts - beim Umschalten der Darstellung saehe man bis zur
// saehe man bis zur ersten Sekundenprobe eine leere Flaeche. // ersten Sekundenprobe eine leere Flaeche.
int32_t sollT = (int32_t)(soll * 10.0f); (void)soll;
lv_chart_set_range(ch, LV_CHART_AXIS_PRIMARY_Y, sollT - 60, sollT + 30);
lv_chart_set_all_value(ch, *ser, (int32_t)(jetzt * 10.0f)); lv_chart_set_all_value(ch, *ser, (int32_t)(jetzt * 10.0f));
spark_range_fit(ch, *ser);
return ch; return ch;
} }
@@ -1677,17 +1721,14 @@ static void chart_duty_btn_cb(lv_event_t*) {
chart_redraw(); chart_redraw();
// Verlaufskurven des Dashboards (Darstellung "Verlauf") mitfuehren. Der Ausschnitt // Verlaufskurven des Dashboards (Darstellung "Verlauf") mitfuehren. Der Ausschnitt
// haengt am Sollwert, damit die Abweichung sichtbar wird und nicht in der Skala // richtet sich nach den vorhandenen Werten, siehe spark_range_fit().
// untergeht - eine feste Skala von 0 bis 130 Grad zeigte nur eine gerade Linie.
if (g_sparkSerW && g_sparkW) { 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)); 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) {
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)); lv_chart_set_next_value(g_sparkD, g_sparkSerD, (int32_t)(g_state.tempD * 10.0f));
spark_range_fit(g_sparkD, g_sparkSerD);
} }
} }
static void build_chart(lv_obj_t* p) { static void build_chart(lv_obj_t* p) {
@@ -3563,7 +3604,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 +3614,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 +4058,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 +4252,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 -