From 26a4e9b95c04c8af10831b283c8cf646b581c1b9 Mon Sep 17 00:00:00 2001
From: MaPaLo76 <72209721+MaPaLo76@users.noreply.github.com>
Date: Sun, 22 Feb 2026 14:15:18 +0100
Subject: [PATCH] fix(display): redesign module layout - integer minutes,
dedicated error slots
New module assignment:
Module 0 : WiFi error indicator (showWifiError) - 'W' / blank
Module 1-3 : laser time in full minutes, 3-digit right-aligned
Module 4 : MQTT error indicator (showMqttError) - 'M' / blank
Module 5-7 : countdown seconds / idle / status, 3-digit right-aligned
Changes in display_manager.h:
- Update zone layout comments
- showLaserTime: integer minutes only, writes modules 1-3 (module 0 untouched)
- showCountdown: writes modules 5-7 only (module 4 untouched)
- showIdle: ' --' on modules 5-7
- showStatus: 3-char string on modules 5-7
- Add showWifiError(bool): module 0
- Add showMqttError(bool): module 4
Changes in display_manager.cpp:
- Add BMP_M character bitmap
- Add 'M' case in charBitmap()
- Rewrite showLaserTime() - round to int, 3 chars, modules 1-3
- Rewrite showCountdown() - 3 chars, modules 5-7
- Rewrite showIdle() - ' --' on modules 5-7
- Rewrite showStatus() - 3 chars, modules 5-7
- Add showWifiError() / showMqttError() implementations
- Update printToSerial() log output
Changes in test_sketch:
- 9 test steps covering all new methods incl. combination test
- Tested on hardware: all steps passed
---
doc/Front.svg | 263 +++++++++++++++++++++++++
include/display_manager.h | 56 ++++--
src/display_manager.cpp | 112 ++++++-----
test_sketches/test_display_manager.cpp | 150 +++++++++-----
4 files changed, 465 insertions(+), 116 deletions(-)
create mode 100644 doc/Front.svg
diff --git a/doc/Front.svg b/doc/Front.svg
new file mode 100644
index 0000000..da827e7
--- /dev/null
+++ b/doc/Front.svg
@@ -0,0 +1,263 @@
+
+
+
+
diff --git a/include/display_manager.h b/include/display_manager.h
index 4b6dd0b..b8bd0df 100644
--- a/include/display_manager.h
+++ b/include/display_manager.h
@@ -8,16 +8,20 @@
// 90°-CW Verdrehung der Module per Software (rotateCCW) zuverlässig kompensiert
// werden kann – ohne dass MD_Parola Zonenlogik interferiert.
//
-// Zone-Aufteilung:
-// Zone 0 (top): Module 0–3 → akkumulierte Laserzeit (Float, Minuten)
-// Zone 1 (bottom): Module 4–7 → Countdown / Status
+// Modul-Aufteilung (oben links = Index 0):
+// Modul 0 : WiFi-Fehler-Indikator (showWifiError)
+// Modul 1–3 (oben) : Laserzeit in ganzen Minuten, 3 Stellen rechtsbündig
+// Modul 4 : MQTT-Fehler-Indikator (showMqttError)
+// Modul 5–7 (unten) : Countdown-Sekunden, 3 Stellen rechtsbündig / showIdle
//
// Verwendung:
-// display.begin(); // einmalig in setup()
-// display.showLaserTime(42.5f);
-// display.showCountdown(18);
-// display.showIdle();
-// display.update(); // in loop() aufrufen (Pflicht)
+// display.begin(); // einmalig in setup()
+// display.showLaserTime(42.5f); // Modul 1-3
+// display.showCountdown(18); // Modul 5-7
+// display.showIdle(); // Modul 5-7
+// display.showWifiError(true); // Modul 0
+// display.showMqttError(true); // Modul 4
+// display.update(); // in loop() aufrufen (Pflicht)
// =============================================================================
#include
@@ -42,24 +46,40 @@ public:
// ---- Anzeige-Methoden ---------------------------------------------------
- // Obere Zone: Laserzeit in Minuten
- // < 10 → " x.x" z.B. " 9.5"
- // < 100 → "xx.x" z.B. "42.5"
- // < 1000 → " xxx" z.B. " 123"
- // < 10000 → "xxxx" z.B. "1234"
- // >= 10000 → "!!!!" (Überlauf)
+ // Modul 1–3 (oben): Laserzeit in ganzen Minuten, 3 Stellen rechtsbündig
+ // 0–9 → " x" z.B. " 7"
+ // 10–99 → " xx" z.B. " 42"
+ // 100–999 → "xxx" z.B. "123"
+ // ≥ 1000 → "!!!" (Überlauf, wird laut Anforderung nie erreicht)
+ // Modul 0 bleibt unberührt.
void showLaserTime(float minutes);
- // Untere Zone: Countdown-Sekunden (rechtsbündig)
- // 0–999s werden dargestellt, > 999 → " !!!"
+ // Modul 5–7 (unten): Countdown-Sekunden, 3 Stellen rechtsbündig
+ // 0–9 → " x"
+ // 10–99 → " xx"
+ // 100–999 → "xxx"
+ // ≥ 1000 → "!!!"
+ // Modul 4 bleibt unberührt.
void showCountdown(int seconds);
- // Untere Zone: Leerlauf-Anzeige (" --")
+ // Modul 5–7 (unten): Leerlauf-Anzeige (" --")
+ // Modul 4 bleibt unberührt.
void showIdle();
- // Untere Zone: Statustext (max. 4 Zeichen, wird abgeschnitten / aufgefüllt)
+ // Modul 5–7 (unten): Statustext (max. 3 Zeichen, wird abgeschnitten / aufgefüllt)
+ // Modul 4 bleibt unberührt.
void showStatus(const char* msg);
+ // Modul 0 (oben links): WiFi-Fehler-Indikator
+ // error=true → 'W' Symbol
+ // error=false → leer
+ void showWifiError(bool error);
+
+ // Modul 4 (unten links): MQTT-Fehler-Indikator
+ // error=true → 'M' Symbol
+ // error=false → leer
+ void showMqttError(bool error);
+
// Beide Zonen löschen
void clear();
diff --git a/src/display_manager.cpp b/src/display_manager.cpp
index 262ec92..910182c 100644
--- a/src/display_manager.cpp
+++ b/src/display_manager.cpp
@@ -110,6 +110,10 @@ static const uint8_t BMP_F[8] = {
0b01111110, 0b01000000, 0b01000000, 0b01111100,
0b01000000, 0b01000000, 0b01000000, 0b00000000
};
+static const uint8_t BMP_M[8] = {
+ 0b01000001, 0b01100011, 0b01010101, 0b01001001,
+ 0b01000001, 0b01000001, 0b01000001, 0b00000000
+};
// ---------------------------------------------------------------------------
// Lookup: ASCII-Zeichen → Bitmap-Zeiger
@@ -139,6 +143,7 @@ const uint8_t* DisplayManager::charBitmap(char c) {
case 'W': return BMP_W;
case 'i': return BMP_i;
case 'F': return BMP_F;
+ case 'M': return BMP_M;
default: return BMP_SPACE;
}
}
@@ -213,85 +218,94 @@ void DisplayManager::writeZone(uint8_t zone, const char* str) {
}
// ---------------------------------------------------------------------------
-// Laserzeit oben anzeigen (Zone 0)
+// Laserzeit oben anzeigen (Module 1–3, ganze Minuten, 3-stellig rechtsbündig)
+// Modul 0 (WiFi-Fehler-Reservierung) wird NICHT verändert.
// ---------------------------------------------------------------------------
void DisplayManager::showLaserTime(float minutes) {
- char buf[DISP_CHARS_PER_ZONE + 1];
-
if (minutes < 0.0f) minutes = 0.0f;
+ int mins = (int)(minutes + 0.5f); // kaufmännisch runden
- if (minutes < 10.0f) {
- // " x.x" → Trennzeichen in Position 2
- int whole = (int)minutes;
- int frac = (int)((minutes - whole) * 10.0f + 0.5f);
- if (frac >= 10) { whole++; frac = 0; }
- snprintf(buf, sizeof(buf), " %d.%d", whole, frac);
- } else if (minutes < 100.0f) {
- // "xx.x"
- int whole = (int)minutes;
- int frac = (int)((minutes - whole) * 10.0f + 0.5f);
- if (frac >= 10) { whole++; frac = 0; }
- snprintf(buf, sizeof(buf), "%2d.%d", whole, frac);
- } else if (minutes < 1000.0f) {
- // " xxx"
- snprintf(buf, sizeof(buf), "%4d", (int)(minutes + 0.5f));
- } else if (minutes < 10000.0f) {
- // "xxxx"
- snprintf(buf, sizeof(buf), "%4d", (int)(minutes + 0.5f));
+ char buf[4]; // 3 Zeichen + Nulltermin
+ if (mins < 10) {
+ snprintf(buf, sizeof(buf), " %d", mins);
+ } else if (mins < 100) {
+ snprintf(buf, sizeof(buf), " %d", mins);
+ } else if (mins < 1000) {
+ snprintf(buf, sizeof(buf), "%d", mins);
} else {
- strlcpy(buf, "!!!!", sizeof(buf));
+ strlcpy(buf, "!!!", sizeof(buf));
}
- // snprintf kann Länge > 4 produzieren wenn Rounding unerwartet → absichern
- if (strlen(buf) != DISP_CHARS_PER_ZONE) {
- strlcpy(buf, "!!!!", sizeof(buf));
- }
+ // Absicherung: genau 3 Zeichen garantieren
+ if (strlen(buf) != 3) strlcpy(buf, "!!!", sizeof(buf));
- writeZone(DISPLAY_ZONE_TOP, buf);
+ // Nur Module 1, 2, 3 schreiben – Modul 0 bleibt unberührt
+ writeChar(1, buf[0]);
+ writeChar(2, buf[1]);
+ writeChar(3, buf[2]);
}
// ---------------------------------------------------------------------------
-// Countdown-Sekunden unten anzeigen (Zone 1), rechtsbündig
+// Countdown-Sekunden unten anzeigen (Module 5–7, 3-stellig rechtsbündig)
+// Modul 4 (MQTT-Fehler-Reservierung) wird NICHT verändert.
// ---------------------------------------------------------------------------
void DisplayManager::showCountdown(int seconds) {
- char buf[DISP_CHARS_PER_ZONE + 1];
-
if (seconds < 0) seconds = 0;
+ char buf[4]; // 3 Zeichen + Nulltermin
if (seconds < 10) {
- snprintf(buf, sizeof(buf), " %d", seconds);
- } else if (seconds < 100) {
snprintf(buf, sizeof(buf), " %d", seconds);
- } else if (seconds < 1000) {
+ } else if (seconds < 100) {
snprintf(buf, sizeof(buf), " %d", seconds);
+ } else if (seconds < 1000) {
+ snprintf(buf, sizeof(buf), "%d", seconds);
} else {
- strlcpy(buf, " !!!", sizeof(buf));
+ strlcpy(buf, "!!!", sizeof(buf));
}
- if (strlen(buf) != DISP_CHARS_PER_ZONE) {
- strlcpy(buf, " !!!", sizeof(buf));
- }
+ if (strlen(buf) != 3) strlcpy(buf, "!!!", sizeof(buf));
- writeZone(DISPLAY_ZONE_BOTTOM, buf);
+ // Nur Module 5, 6, 7 schreiben – Modul 4 bleibt unberührt
+ writeChar(5, buf[0]);
+ writeChar(6, buf[1]);
+ writeChar(7, buf[2]);
}
// ---------------------------------------------------------------------------
-// Leerlauf-Anzeige unten (" --")
+// Leerlauf-Anzeige unten (" --" auf Module 5–7)
// ---------------------------------------------------------------------------
void DisplayManager::showIdle() {
- writeZone(DISPLAY_ZONE_BOTTOM, " --");
+ writeChar(5, ' ');
+ writeChar(6, '-');
+ writeChar(7, '-');
}
// ---------------------------------------------------------------------------
-// Statustext unten (max. 4 Zeichen)
+// Statustext unten (max. 3 Zeichen auf Module 5–7)
// ---------------------------------------------------------------------------
void DisplayManager::showStatus(const char* msg) {
- char buf[DISP_CHARS_PER_ZONE + 1] = " "; // mit Leerzeichen vorbelegen
+ char buf[4] = " "; // 3 Leerzeichen als Vorlage
size_t len = strlen(msg);
- if (len > DISP_CHARS_PER_ZONE) len = DISP_CHARS_PER_ZONE;
+ if (len > 3) len = 3;
memcpy(buf, msg, len);
- buf[DISP_CHARS_PER_ZONE] = '\0';
- writeZone(DISPLAY_ZONE_BOTTOM, buf);
+ buf[3] = '\0';
+ writeChar(5, buf[0]);
+ writeChar(6, buf[1]);
+ writeChar(7, buf[2]);
+}
+
+// ---------------------------------------------------------------------------
+// WiFi-Fehler-Indikator auf Modul 0
+// ---------------------------------------------------------------------------
+void DisplayManager::showWifiError(bool error) {
+ writeChar(0, error ? 'W' : ' ');
+}
+
+// ---------------------------------------------------------------------------
+// MQTT-Fehler-Indikator auf Modul 4
+// ---------------------------------------------------------------------------
+void DisplayManager::showMqttError(bool error) {
+ writeChar(4, error ? 'M' : ' ');
}
// ---------------------------------------------------------------------------
@@ -323,8 +337,8 @@ void DisplayManager::allLedsOff() {
void DisplayManager::printToSerial() const {
LOG_I("DISP", "DisplayManager: %d Module, HW: GENERIC_HW, CS: GPIO%d",
DISPLAY_MODULE_COUNT, DISPLAY_CS_PIN);
- LOG_I("DISP", "Zone 0 (oben): Module 0-%d = Laserzeit",
- DISPLAY_MODULES_PER_ZONE - 1);
- LOG_I("DISP", "Zone 1 (unten): Module %d-%d = Countdown/Status",
- DISPLAY_MODULES_PER_ZONE, DISPLAY_MODULE_COUNT - 1);
+ LOG_I("DISP", "Modul 0 : WiFi-Fehler (showWifiError)");
+ LOG_I("DISP", "Module 1-3 : Laserzeit in ganzen Minuten");
+ LOG_I("DISP", "Modul 4 : MQTT-Fehler (showMqttError)");
+ LOG_I("DISP", "Module 5-7 : Countdown/Status");
}
diff --git a/test_sketches/test_display_manager.cpp b/test_sketches/test_display_manager.cpp
index 79588d9..6cdb6cb 100644
--- a/test_sketches/test_display_manager.cpp
+++ b/test_sketches/test_display_manager.cpp
@@ -1,23 +1,31 @@
-/**
- * TEST SKETCH 4.3 – DisplayManager Verifikation
+/**
+ * TEST SKETCH 4.3 - DisplayManager Verifikation (angepasstes Layout)
*
- * Testet alle Methoden des DisplayManagers:
+ * Modul-Aufteilung:
+ * Modul 0 : WiFi-Fehler-Indikator (showWifiError)
+ * Module 1-3 : Laserzeit in ganzen Minuten, 3-stellig rechtsbuendig
+ * Modul 4 : MQTT-Fehler-Indikator (showMqttError)
+ * Module 5-7 : Countdown-Sekunden / Idle / Status, 3-stellig rechtsbuendig
+ *
+ * Testet:
* 1. Alle LEDs EIN/AUS (Modul-Check)
- * 2. showLaserTime() mit verschiedenen Werten (Grenzwerttest)
- * 3. showCountdown() hochzählend
- * 4. showIdle()
- * 5. showStatus() mit "Err", "AP", "WiFi"
- * 6. Realistischer Loop: laufende Laserzeit + Countdown simuliert
+ * 2. WiFi-Fehler EIN/AUS (Modul 0)
+ * 3. MQTT-Fehler EIN/AUS (Modul 4)
+ * 4. showLaserTime() Grenzwerte: ganze Minuten, Module 1-3
+ * 5. showCountdown() Grenzwerte: Module 5-7
+ * 6. showIdle()
+ * 7. showStatus() mit 3-Zeichen-Strings
+ * 8. Kombinations-Test: WiFi+MQTT Fehler gleichzeitig mit Laserzeit+Countdown
+ * 9. Realistischer Loop
*
- * Flash: pio run -e test-display-mgr --target upload
+ * Flash: pio run -e test-display-mgr --target upload
* Monitor: pio device monitor -e test-display-mgr
*/
#include
#include "display_manager.h"
-// Pause zwischen Testschritten
-#define STEP_MS 2000
+#define STEP_MS 1500
static void step(const char* desc) {
Serial.printf("[STEP] %s\n", desc);
@@ -30,92 +38,136 @@ void setup() {
delay(500);
Serial.println("\n========================================");
- Serial.println(" TEST 4.3 – DisplayManager");
+ Serial.println(" TEST 4.3 - DisplayManager (neues Layout)");
Serial.println("========================================");
display.begin();
display.printToSerial();
// ------------------------------------------------------------------
- // 1. Alle LEDs EIN → alle 8 Module müssen leuchten
+ // 1. Alle LEDs EIN -> alle 8 Module muessen leuchten
// ------------------------------------------------------------------
- Serial.println("\n[1] Alle LEDs EIN (2s) – alle 8 Module pruefen");
+ Serial.println("\n[1] Alle LEDs EIN (2s) - alle 8 Module pruefen");
display.allLedsOn();
delay(2000);
display.allLedsOff();
delay(500);
// ------------------------------------------------------------------
- // 2. showLaserTime() – Grenzwerttests
+ // 2. WiFi-Fehler Modul 0
// ------------------------------------------------------------------
- Serial.println("\n[2] showLaserTime() Grenzwerte:");
+ Serial.println("\n[2] WiFi-Fehler Modul 0:");
+ Serial.println(" EIN: Modul 0 zeigt 'W'");
+ display.showWifiError(true);
+ step("Erwarte: Modul 0 = 'W', Rest leer");
+ Serial.println(" AUS: Modul 0 leer");
+ display.showWifiError(false);
+ step("Erwarte: Modul 0 = leer");
- float testTimes[] = { 0.0f, 0.5f, 1.0f, 9.9f, 10.0f, 42.5f, 99.9f,
- 100.0f, 123.0f, 999.0f, 1000.0f, 9999.0f };
- for (float t : testTimes) {
- Serial.printf(" %.1f min → oben\n", t);
- display.showLaserTime(t);
- delay(1500);
+ // ------------------------------------------------------------------
+ // 3. MQTT-Fehler Modul 4
+ // ------------------------------------------------------------------
+ Serial.println("\n[3] MQTT-Fehler Modul 4:");
+ Serial.println(" EIN: Modul 4 zeigt 'M'");
+ display.showMqttError(true);
+ step("Erwarte: Modul 4 = 'M', Rest leer");
+ Serial.println(" AUS: Modul 4 leer");
+ display.showMqttError(false);
+ step("Erwarte: Modul 4 = leer");
+
+ // ------------------------------------------------------------------
+ // 4. showLaserTime() - ganze Minuten, Module 1-3
+ // ------------------------------------------------------------------
+ Serial.println("\n[4] showLaserTime() Grenzwerte (Module 1-3, ganze Minuten):");
+ int testMins[] = { 0, 1, 9, 10, 42, 99, 100, 123, 999 };
+ for (int m : testMins) {
+ Serial.printf(" %d min\n", m);
+ display.showLaserTime((float)m);
+ delay(1200);
}
display.clear();
- delay(500);
+ delay(300);
// ------------------------------------------------------------------
- // 3. showCountdown() von 120 → 0 (jede Sekunde)
+ // 5. showCountdown() - Module 5-7
// ------------------------------------------------------------------
- Serial.println("\n[3] showCountdown() 5..0:");
- for (int s = 5; s >= 0; s--) {
+ Serial.println("\n[5] showCountdown() 9..0 (Module 5-7):");
+ for (int s = 9; s >= 0; s--) {
Serial.printf(" %d s\n", s);
display.showCountdown(s);
- delay(1000);
+ delay(700);
}
- delay(500);
+ Serial.println(" 120 s");
+ display.showCountdown(120);
+ delay(STEP_MS);
+ display.clear();
+ delay(300);
// ------------------------------------------------------------------
- // 4. showIdle()
+ // 6. showIdle() - Module 5-7
// ------------------------------------------------------------------
- Serial.println("\n[4] showIdle() (2s)");
+ Serial.println("\n[6] showIdle() (Module 5-7)");
display.showIdle();
- step("Erwarte ' --' auf unterer Reihe");
+ step("Erwarte: Module 5-7 = ' --', Modul 4 leer");
// ------------------------------------------------------------------
- // 5. showStatus() mit verschiedenen Strings
+ // 7. showStatus() mit 3-Zeichen-Strings
// ------------------------------------------------------------------
- Serial.println("\n[5] showStatus() Tests:");
- const char* statMsgs[] = { "Err ", "AP ", "WiFi", " oF" };
- for (const char* m : statMsgs) {
+ Serial.println("\n[7] showStatus() Tests (Module 5-7):");
+ const char* msgs[] = { "Err", "AP ", "oFF", " " };
+ for (const char* m : msgs) {
Serial.printf(" '%s'\n", m);
display.showStatus(m);
delay(STEP_MS);
}
display.clear();
- delay(500);
+ delay(300);
// ------------------------------------------------------------------
- // 6. Realistischer Betrieb: Laserzeit wächst, Countdown läuft
+ // 8. Kombinations-Test: WiFi+MQTT Fehler + Laserzeit + Countdown
// ------------------------------------------------------------------
- Serial.println("\n[6] Realistischer Betrieb (30s): Laserzeit + Countdown");
- Serial.println(" Erwarte: oben steigt, unten zaehlt runter, dann '--'");
+ Serial.println("\n[8] Kombinations-Test:");
+ Serial.println(" Modul 0='W', 1-3=042, Modul 4='M', 5-7= 20");
+ display.showWifiError(true);
+ display.showLaserTime(42.0f);
+ display.showMqttError(true);
+ display.showCountdown(20);
+ step("Erwarte: W | 042 | M | 20");
+
+ Serial.println(" Fehler weg: Modul 0 und 4 leer");
+ display.showWifiError(false);
+ display.showMqttError(false);
+ step("Erwarte: leer | 042 | leer | 20");
+
+ display.clear();
+ delay(300);
+
+ // ------------------------------------------------------------------
+ // 9. Realistischer Betrieb
+ // ------------------------------------------------------------------
+ Serial.println("\n[9] Realistischer Betrieb: Laserzeit steigt, Countdown->Idle");
+ display.showWifiError(false);
+ display.showMqttError(false);
}
// ---------------------------------------------------------------------------
void loop() {
- static float totalMin = 42.3f;
- static int countdown = 20;
- static bool counting = true;
- static uint32_t lastSec = 0;
- static uint32_t lastTenth = 0;
+ static float totalMin = 42.0f;
+ static int countdown = 20;
+ static bool counting = true;
+ static uint32_t lastSec = 0;
+ static uint32_t lastTenth = 0;
uint32_t now = millis();
- // Jede 100ms: Laserzeit um ~0.001 min erhöhen (0.06 min/min = 1x Normal)
+ // Alle 100ms: Laserzeit um ~0.001 min erhoehen
if (now - lastTenth >= 100) {
lastTenth = now;
totalMin += 0.001f;
display.showLaserTime(totalMin);
}
- // Jede Sekunde: Countdown herunterzählen
+ // Jede Sekunde: Countdown herunterzaehlen
if (now - lastSec >= 1000) {
lastSec = now;
if (counting && countdown > 0) {
@@ -124,8 +176,8 @@ void loop() {
} else if (counting && countdown == 0) {
counting = false;
display.showIdle();
- Serial.println("[LIVE] Countdown abgelaufen → Idle");
+ Serial.println("[LIVE] Countdown abgelaufen -> Idle");
}
- Serial.printf("[LIVE] %.2f min, countdown=%d\n", totalMin, countdown);
+ Serial.printf("[LIVE] %d min, countdown=%d\n", (int)(totalMin + 0.5f), countdown);
}
-}
+}
\ No newline at end of file