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 @@ + + + + + + + + + + + + + + + + + + + + + + + + Freie Testzeit + in Sekunden + kostenpflichtige + Laser Cutter Zeiterfassung + Laserzeit in Minuten + Wifi + Error + MQTT + Error + + + + 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