feat(display): implement DisplayManager with raw MD_MAX72XX

- Add include/display_manager.h: DisplayManager class declaration
  - Two-zone layout: Zone 0 (top, laser time), Zone 1 (bottom, countdown/status)
  - showLaserTime(), showCountdown(), showIdle(), showStatus(), setBrightness()
  - rotateCCW() bitmap transformation for 90 deg physical module rotation
  - charBitmap() for 17-character set (0-9, space, dash, dot, special chars)

- Add src/display_manager.cpp: full implementation
  - Double-init pattern for SPI power stability
  - showLaserTime() format: <10 -> ' x.x', <100 -> 'xx.x', <1000 -> ' xxx', else 'xxxx'
  - showCountdown() right-aligned 4-char format
  - All methods use writeZone() -> writeChar() -> rotateCCW() -> MD_MAX72XX

- Add test_sketches/test_display_manager.cpp: 6-step verification test
  - allLedsOn/Off, showLaserTime (12 boundary values), showCountdown 5->0
  - showIdle, showStatus (Err/AP/WiFi/oF), live simulation loop

- Update platformio.ini: add test-display-mgr environment
- Update src/main.cpp: integrate display.begin/showIdle/update
- Update Implementation-Plan.md: mark Phase 4 tasks 4.1-4.3 complete

Tested on hardware: all 6 test steps passed
This commit is contained in:
MaPaLo76 2026-02-22 14:00:54 +01:00
parent c63cace1ae
commit 4349b37f05
6 changed files with 602 additions and 9 deletions

View File

@ -77,19 +77,29 @@
## Phase 4 Dot-Matrix-Display (`DisplayManager`)
- [ ] **4.1** `include/display_manager.h` + `src/display_manager.cpp` erstellen
Wrapper um `MD_Parola` mit zwei Zonen:
- [x] **4.1** `include/display_manager.h` + `src/display_manager.cpp` erstellen
Implementierung mit rohem `MD_MAX72XX` (nicht MD_Parola) für vollständige Rotationskontrolle:
- Zone 0 (Module 03, obere Reihe): Laserzeit in Minuten
- Zone 1 (Module 47, untere Reihe): Countdown / Statusmeldungen
- `rotateCCW()` kompensiert physische 90°-CW-Verdrehung der Module
- 17 Zeichen-Bitmaps definiert (09, Sonderzeichen, Buchstaben)
- [ ] **4.2** Methoden implementieren:
- `showLaserTime(float minutes)` obere Zeile
- `showCountdown(int seconds)` untere Zeile (während Gratiszeit)
- `showIdle()` `---` in unterer Zeile wenn kein Countdown
- `showError(const char* msg)` Fehlermeldung in unterer Zeile, Modul 1
- `update()` in `loop()` aufrufen (MD_Parola benötigt regelmäßigen Aufruf)
- [x] **4.2** Methoden implementiert:
- `showLaserTime(float minutes)` obere Zeile (4 Formate je Wertebereich)
- `showCountdown(int seconds)` untere Zeile, rechtsbündig (während Gratiszeit)
- `showIdle()` ` --` in unterer Zeile wenn kein Countdown aktiv
- `showStatus(const char* msg)` max. 4-Zeichen-Statusmeldung (untere Zeile)
- `setBrightness()`, `allLedsOn()`, `allLedsOff()`, `clear()`, `printToSerial()`
- `update()` in `loop()` aufrufen (no-op, Interface für künftige Animationen)
- `main.cpp` integriert: `display.begin()`, `display.showIdle()`, `display.update()`
- [ ] **4.3** Test: Statische Zahlen und Scrolltext auf beiden Zonen anzeigen
- [x] **4.3** Test: `test_sketches/test_display_manager.cpp`, Environment `test-display-mgr`
- Alle LEDs EIN/AUS ✅
- `showLaserTime()` alle 12 Grenzwerte (0.0 9999.0 min) ✅
- `showCountdown()` 5→0 ✅
- `showIdle()`` --`
- `showStatus()` mit „Err ", „AP ", „WiFi", „ oF" ✅
- Realistischer Loop: Laserzeit steigt, Countdown 20→0, dann Idle ✅
---

97
include/display_manager.h Normal file
View File

@ -0,0 +1,97 @@
#pragma once
// =============================================================================
// display_manager.h Dot-Matrix-Display Ausgabe (8× GYMAX7219, 4×2 Layout)
// Projekt: MQTT-Display LaserCutter
//
// Implementierung mit rohem MD_MAX72XX (kein MD_Parola), damit die physische
// 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 03 → akkumulierte Laserzeit (Float, Minuten)
// Zone 1 (bottom): Module 47 → Countdown / Status
//
// Verwendung:
// display.begin(); // einmalig in setup()
// display.showLaserTime(42.5f);
// display.showCountdown(18);
// display.showIdle();
// display.update(); // in loop() aufrufen (Pflicht)
// =============================================================================
#include <Arduino.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "config.h"
// ---------------------------------------------------------------------------
// Maximale Textlänge je Zone (= Anzahl Module pro Zone)
// ---------------------------------------------------------------------------
#define DISP_CHARS_PER_ZONE DISPLAY_MODULES_PER_ZONE // 4
class DisplayManager {
public:
DisplayManager();
// Initialisierung (einmalig in setup())
void begin();
// Helligkeit setzen 015
void setBrightness(uint8_t level);
// ---- 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)
void showLaserTime(float minutes);
// Untere Zone: Countdown-Sekunden (rechtsbündig)
// 0999s werden dargestellt, > 999 → " !!!"
void showCountdown(int seconds);
// Untere Zone: Leerlauf-Anzeige (" --")
void showIdle();
// Untere Zone: Statustext (max. 4 Zeichen, wird abgeschnitten / aufgefüllt)
void showStatus(const char* msg);
// Beide Zonen löschen
void clear();
// Muss in loop() aufgerufen werden (reserviert für zukünftige Scroll-Animation)
void update() {}
// Alle LEDs ein / aus (Testfunktion)
void allLedsOn();
void allLedsOff();
// Debug-Ausgabe auf Serial
void printToSerial() const;
private:
MD_MAX72XX _mx;
// Schreibt genau DISP_CHARS_PER_ZONE Zeichen auf eine Zone
// zone 0 → Module 0..3 (oben)
// zone 1 → Module 4..7 (unten)
// str muss exakt DISP_CHARS_PER_ZONE Zeichen enthalten (kein Null-Terminator nötig)
void writeZone(uint8_t zone, const char* str);
// Schreibt ein Zeichen auf ein einzelnes Modul
void writeChar(uint8_t moduleIdx, char c);
// 90° CCW Rotation kompensiert physische 90° CW Verdrehung der Module
static void rotateCCW(const uint8_t src[8], uint8_t dst[8]);
// Gibt Zeiger auf 8-Byte-Bitmap für ASCII-Zeichen zurück
// Unterstützt: '0''9', ' ', '-', '.', '!', 'E', 'r', 'o', 'n', 'A', 'P'
static const uint8_t* charBitmap(char c);
};
// Globale Instanz
extern DisplayManager display;

View File

@ -108,3 +108,22 @@ build_flags =
lib_deps =
tzapu/WiFiManager @ ^2.0.17
majicDesigns/MD_MAX72XX @ ^3.5.1
; =============================================================================
; TEST ENVIRONMENT 4.3 DisplayManager Verifikation
; Flash: pio run -e test-display-mgr --target upload
; Monitor: pio device monitor -e test-display-mgr
; =============================================================================
[env:test-display-mgr]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
upload_port = COM3
monitor_speed = 115200
monitor_echo = yes
build_src_filter = -<*> +<../test_sketches/test_display_manager.cpp> +<../src/display_manager.cpp>
build_flags =
-DCORE_DEBUG_LEVEL=1
-Wno-cpp
lib_deps =
majicDesigns/MD_MAX72XX @ ^3.5.1

330
src/display_manager.cpp Normal file
View File

@ -0,0 +1,330 @@
// =============================================================================
// display_manager.cpp Dot-Matrix-Display Ausgabe
// Verwendet rohen MD_MAX72XX mit manuellem 90° CCW Bitmap-Rotations-
// ausgleich für die physisch 90° CW verbauten GYMAX7219 Module.
// =============================================================================
#include "display_manager.h"
#include <string.h>
// Globale Instanz
DisplayManager display;
// ---------------------------------------------------------------------------
// Zeichensatz-Bitmaps
// Darstellung in AUFRECHTER (normaler) Orientierung. rotateCCW() kompensiert
// die physische 90° CW Verdrehung der Module beim Schreiben auf die Hardware.
// Je Eintrag: 8 Bytes = 8 Pixelzeilen von oben (Index 0) nach unten (Index 7),
// Bit 7 = linke Spalte, Bit 0 = rechte Spalte.
// ---------------------------------------------------------------------------
static const uint8_t BMP_0[8] = {
0b00111100, 0b01000010, 0b01000110, 0b01001010,
0b01010010, 0b01100010, 0b00111100, 0b00000000
};
static const uint8_t BMP_1[8] = {
0b00010000, 0b00110000, 0b00010000, 0b00010000,
0b00010000, 0b00010000, 0b00111000, 0b00000000
};
static const uint8_t BMP_2[8] = {
0b00111100, 0b01000010, 0b00000010, 0b00001100,
0b00010000, 0b00100000, 0b01111110, 0b00000000
};
static const uint8_t BMP_3[8] = {
0b00111100, 0b01000010, 0b00000010, 0b00011100,
0b00000010, 0b01000010, 0b00111100, 0b00000000
};
static const uint8_t BMP_4[8] = {
0b00001000, 0b00011000, 0b00101000, 0b01001000,
0b01111110, 0b00001000, 0b00001000, 0b00000000
};
static const uint8_t BMP_5[8] = {
0b01111110, 0b01000000, 0b01111100, 0b00000010,
0b00000010, 0b01000010, 0b00111100, 0b00000000
};
static const uint8_t BMP_6[8] = {
0b00111100, 0b01000000, 0b01111100, 0b01000010,
0b01000010, 0b01000010, 0b00111100, 0b00000000
};
static const uint8_t BMP_7[8] = {
0b01111110, 0b00000010, 0b00000100, 0b00001000,
0b00010000, 0b00100000, 0b00100000, 0b00000000
};
static const uint8_t BMP_8[8] = {
0b00111100, 0b01000010, 0b01000010, 0b00111100,
0b01000010, 0b01000010, 0b00111100, 0b00000000
};
static const uint8_t BMP_9[8] = {
0b00111100, 0b01000010, 0b01000010, 0b00111110,
0b00000010, 0b01000010, 0b00111100, 0b00000000
};
// Sonderzeichen
static const uint8_t BMP_SPACE[8] = {
0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000
};
static const uint8_t BMP_MINUS[8] = {
0b00000000, 0b00000000, 0b00000000, 0b01111110,
0b00000000, 0b00000000, 0b00000000, 0b00000000
};
static const uint8_t BMP_DOT[8] = { // Dezimalpunkt '.'
0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00110000, 0b00000000
};
static const uint8_t BMP_EXCLAM[8] = { // '!' Überlauf-Anzeige
0b00010000, 0b00010000, 0b00010000, 0b00010000,
0b00010000, 0b00000000, 0b00010000, 0b00000000
};
static const uint8_t BMP_E[8] = {
0b01111110, 0b01000000, 0b01000000, 0b01111100,
0b01000000, 0b01000000, 0b01111110, 0b00000000
};
static const uint8_t BMP_r[8] = { // Kleinbuchstabe 'r'
0b00000000, 0b00000000, 0b01011100, 0b01100000,
0b01000000, 0b01000000, 0b01000000, 0b00000000
};
static const uint8_t BMP_o[8] = { // Kleinbuchstabe 'o'
0b00000000, 0b00000000, 0b00111100, 0b01000010,
0b01000010, 0b01000010, 0b00111100, 0b00000000
};
static const uint8_t BMP_n[8] = {
0b00000000, 0b00000000, 0b01111000, 0b01000100,
0b01000010, 0b01000010, 0b01000010, 0b00000000
};
static const uint8_t BMP_A[8] = {
0b00011000, 0b00100100, 0b01000010, 0b01111110,
0b01000010, 0b01000010, 0b01000010, 0b00000000
};
static const uint8_t BMP_P[8] = {
0b01111100, 0b01000010, 0b01000010, 0b01111100,
0b01000000, 0b01000000, 0b01000000, 0b00000000
};
static const uint8_t BMP_W[8] = {
0b01000001, 0b01000001, 0b01000001, 0b01010101,
0b01010101, 0b01100011, 0b01000001, 0b00000000
};
static const uint8_t BMP_i[8] = {
0b00001000, 0b00000000, 0b00001000, 0b00001000,
0b00001000, 0b00001000, 0b00001110, 0b00000000
};
static const uint8_t BMP_F[8] = {
0b01111110, 0b01000000, 0b01000000, 0b01111100,
0b01000000, 0b01000000, 0b01000000, 0b00000000
};
// ---------------------------------------------------------------------------
// Lookup: ASCII-Zeichen → Bitmap-Zeiger
// ---------------------------------------------------------------------------
const uint8_t* DisplayManager::charBitmap(char c) {
switch (c) {
case '0': return BMP_0;
case '1': return BMP_1;
case '2': return BMP_2;
case '3': return BMP_3;
case '4': return BMP_4;
case '5': return BMP_5;
case '6': return BMP_6;
case '7': return BMP_7;
case '8': return BMP_8;
case '9': return BMP_9;
case ' ': return BMP_SPACE;
case '-': return BMP_MINUS;
case '.': return BMP_DOT;
case '!': return BMP_EXCLAM;
case 'E': return BMP_E;
case 'r': return BMP_r;
case 'o': return BMP_o;
case 'n': return BMP_n;
case 'A': return BMP_A;
case 'P': return BMP_P;
case 'W': return BMP_W;
case 'i': return BMP_i;
case 'F': return BMP_F;
default: return BMP_SPACE;
}
}
// ---------------------------------------------------------------------------
// 90° CCW Rotation kompensiert physische 90° CW Verdrehung der Module
// ---------------------------------------------------------------------------
void DisplayManager::rotateCCW(const uint8_t src[8], uint8_t dst[8]) {
memset(dst, 0, 8);
for (uint8_t r = 0; r < 8; r++) {
for (uint8_t c = 0; c < 8; c++) {
if (src[c] & (1 << r)) {
dst[r] |= (1 << (7 - c));
}
}
}
}
// ---------------------------------------------------------------------------
// Konstruktor
// ---------------------------------------------------------------------------
DisplayManager::DisplayManager()
: _mx(DISPLAY_HW_TYPE, DISPLAY_CS_PIN, DISPLAY_MODULE_COUNT)
{}
// ---------------------------------------------------------------------------
// Doppelte Initialisierung holt Module nach die beim ersten begin() noch
// nicht bereit waren (Power-On Anlaufverzögerung bei ext. Netzteil)
// ---------------------------------------------------------------------------
static void initMx(MD_MAX72XX& mx, uint8_t brightness) {
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, brightness);
mx.control(MD_MAX72XX::TEST, MD_MAX72XX::OFF);
mx.clear();
}
void DisplayManager::begin() {
initMx(_mx, DISPLAY_BRIGHTNESS);
delay(200);
initMx(_mx, DISPLAY_BRIGHTNESS);
LOG_I("DISP", "DisplayManager initialisiert (GENERIC_HW, %d Module)", DISPLAY_MODULE_COUNT);
}
// ---------------------------------------------------------------------------
void DisplayManager::setBrightness(uint8_t level) {
if (level > 15) level = 15;
_mx.control(MD_MAX72XX::INTENSITY, level);
}
// ---------------------------------------------------------------------------
// Einzelnes Zeichen auf ein Modul schreiben (mit CCW Rotation)
// ---------------------------------------------------------------------------
void DisplayManager::writeChar(uint8_t moduleIdx, char c) {
const uint8_t* bmp = charBitmap(c);
uint8_t rotated[8];
rotateCCW(bmp, rotated);
for (uint8_t row = 0; row < 8; row++) {
_mx.setRow(moduleIdx, row, rotated[row]);
}
}
// ---------------------------------------------------------------------------
// 4-Zeichen-String auf eine Zone schreiben
// zone 0 → Module 03, zone 1 → Module 47
// str muss genau DISP_CHARS_PER_ZONE Zeichen enthalten
// ---------------------------------------------------------------------------
void DisplayManager::writeZone(uint8_t zone, const char* str) {
uint8_t base = zone * DISPLAY_MODULES_PER_ZONE;
for (uint8_t i = 0; i < DISPLAY_MODULES_PER_ZONE; i++) {
writeChar(base + i, str[i]);
}
}
// ---------------------------------------------------------------------------
// Laserzeit oben anzeigen (Zone 0)
// ---------------------------------------------------------------------------
void DisplayManager::showLaserTime(float minutes) {
char buf[DISP_CHARS_PER_ZONE + 1];
if (minutes < 0.0f) minutes = 0.0f;
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));
} else {
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));
}
writeZone(DISPLAY_ZONE_TOP, buf);
}
// ---------------------------------------------------------------------------
// Countdown-Sekunden unten anzeigen (Zone 1), rechtsbündig
// ---------------------------------------------------------------------------
void DisplayManager::showCountdown(int seconds) {
char buf[DISP_CHARS_PER_ZONE + 1];
if (seconds < 0) seconds = 0;
if (seconds < 10) {
snprintf(buf, sizeof(buf), " %d", seconds);
} 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));
}
if (strlen(buf) != DISP_CHARS_PER_ZONE) {
strlcpy(buf, " !!!", sizeof(buf));
}
writeZone(DISPLAY_ZONE_BOTTOM, buf);
}
// ---------------------------------------------------------------------------
// Leerlauf-Anzeige unten (" --")
// ---------------------------------------------------------------------------
void DisplayManager::showIdle() {
writeZone(DISPLAY_ZONE_BOTTOM, " --");
}
// ---------------------------------------------------------------------------
// Statustext unten (max. 4 Zeichen)
// ---------------------------------------------------------------------------
void DisplayManager::showStatus(const char* msg) {
char buf[DISP_CHARS_PER_ZONE + 1] = " "; // mit Leerzeichen vorbelegen
size_t len = strlen(msg);
if (len > DISP_CHARS_PER_ZONE) len = DISP_CHARS_PER_ZONE;
memcpy(buf, msg, len);
buf[DISP_CHARS_PER_ZONE] = '\0';
writeZone(DISPLAY_ZONE_BOTTOM, buf);
}
// ---------------------------------------------------------------------------
// Beide Zonen löschen
// ---------------------------------------------------------------------------
void DisplayManager::clear() {
_mx.clear();
}
// ---------------------------------------------------------------------------
// Alle LEDs ein
// ---------------------------------------------------------------------------
void DisplayManager::allLedsOn() {
for (uint8_t m = 0; m < DISPLAY_MODULE_COUNT; m++) {
for (uint8_t r = 0; r < 8; r++) {
_mx.setRow(m, r, 0xFF);
}
}
}
// ---------------------------------------------------------------------------
// Alle LEDs aus
// ---------------------------------------------------------------------------
void DisplayManager::allLedsOff() {
_mx.clear();
}
// ---------------------------------------------------------------------------
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);
}

View File

@ -2,6 +2,7 @@
#include "config.h"
#include "settings.h"
#include "wifi_connector.h"
#include "display_manager.h"
void setup() {
Serial.begin(SERIAL_BAUD_RATE);
@ -10,11 +11,16 @@ void setup() {
LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN);
settings.printToSerial();
display.begin();
display.printToSerial();
display.showIdle();
wifiConnector.begin();
wifiConnector.printToSerial();
}
void loop() {
wifiConnector.loop();
display.update();
delay(100);
}

View File

@ -0,0 +1,131 @@
/**
* TEST SKETCH 4.3 DisplayManager Verifikation
*
* Testet alle Methoden des DisplayManagers:
* 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
*
* Flash: pio run -e test-display-mgr --target upload
* Monitor: pio device monitor -e test-display-mgr
*/
#include <Arduino.h>
#include "display_manager.h"
// Pause zwischen Testschritten
#define STEP_MS 2000
static void step(const char* desc) {
Serial.printf("[STEP] %s\n", desc);
delay(STEP_MS);
}
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("\n========================================");
Serial.println(" TEST 4.3 DisplayManager");
Serial.println("========================================");
display.begin();
display.printToSerial();
// ------------------------------------------------------------------
// 1. Alle LEDs EIN → alle 8 Module müssen leuchten
// ------------------------------------------------------------------
Serial.println("\n[1] Alle LEDs EIN (2s) alle 8 Module pruefen");
display.allLedsOn();
delay(2000);
display.allLedsOff();
delay(500);
// ------------------------------------------------------------------
// 2. showLaserTime() Grenzwerttests
// ------------------------------------------------------------------
Serial.println("\n[2] showLaserTime() Grenzwerte:");
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);
}
display.clear();
delay(500);
// ------------------------------------------------------------------
// 3. showCountdown() von 120 → 0 (jede Sekunde)
// ------------------------------------------------------------------
Serial.println("\n[3] showCountdown() 5..0:");
for (int s = 5; s >= 0; s--) {
Serial.printf(" %d s\n", s);
display.showCountdown(s);
delay(1000);
}
delay(500);
// ------------------------------------------------------------------
// 4. showIdle()
// ------------------------------------------------------------------
Serial.println("\n[4] showIdle() (2s)");
display.showIdle();
step("Erwarte ' --' auf unterer Reihe");
// ------------------------------------------------------------------
// 5. showStatus() mit verschiedenen Strings
// ------------------------------------------------------------------
Serial.println("\n[5] showStatus() Tests:");
const char* statMsgs[] = { "Err ", "AP ", "WiFi", " oF" };
for (const char* m : statMsgs) {
Serial.printf(" '%s'\n", m);
display.showStatus(m);
delay(STEP_MS);
}
display.clear();
delay(500);
// ------------------------------------------------------------------
// 6. Realistischer Betrieb: Laserzeit wächst, Countdown läuft
// ------------------------------------------------------------------
Serial.println("\n[6] Realistischer Betrieb (30s): Laserzeit + Countdown");
Serial.println(" Erwarte: oben steigt, unten zaehlt runter, dann '--'");
}
// ---------------------------------------------------------------------------
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;
uint32_t now = millis();
// Jede 100ms: Laserzeit um ~0.001 min erhöhen (0.06 min/min = 1x Normal)
if (now - lastTenth >= 100) {
lastTenth = now;
totalMin += 0.001f;
display.showLaserTime(totalMin);
}
// Jede Sekunde: Countdown herunterzählen
if (now - lastSec >= 1000) {
lastSec = now;
if (counting && countdown > 0) {
display.showCountdown(countdown);
countdown--;
} else if (counting && countdown == 0) {
counting = false;
display.showIdle();
Serial.println("[LIVE] Countdown abgelaufen → Idle");
}
Serial.printf("[LIVE] %.2f min, countdown=%d\n", totalMin, countdown);
}
}