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
This commit is contained in:
MaPaLo76 2026-02-22 14:15:18 +01:00
parent 4349b37f05
commit 26a4e9b95c
4 changed files with 465 additions and 116 deletions

263
doc/Front.svg Normal file
View File

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="297mm"
height="420mm"
viewBox="0 0 297 420"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="Front.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.71891835"
inkscape:cx="755.99684"
inkscape:cy="710.09455"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer3" />
<defs
id="defs1">
<rect
x="570.3012"
y="984.81281"
width="239.24831"
height="91.804584"
id="rect4" />
</defs>
<g
inkscape:label="Rahmen"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel"
id="rect1"
width="253"
height="253"
x="22"
y="83.5" />
<rect
style="fill:#ffffff;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel"
id="rect2"
width="217"
height="217"
x="40"
y="101.5" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="DotMatrixDisplay"
transform="translate(5.921936,55.427505)"
sodipodi:insensitive="true">
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3"
width="33.75"
height="33.5"
x="75.078072"
y="154.57249" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-8"
width="33.75"
height="33.5"
x="75.078072"
y="121.07249" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-1"
width="33.75"
height="33.5"
x="108.82807"
y="121.07248" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-1-2"
width="33.75"
height="33.5"
x="108.82807"
y="154.57249" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-2"
width="33.75"
height="33.5"
x="142.57806"
y="154.57249" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-8-3"
width="33.75"
height="33.5"
x="142.57806"
y="121.07248" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-1-22"
width="33.75"
height="33.5"
x="176.32806"
y="121.07246" />
<rect
style="fill:#cccccc;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
id="rect3-1-2-1"
width="33.75"
height="33.5"
x="176.32806"
y="154.57249" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Beschriftung">
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="204.76865"
y="256.40359"
id="text4"><tspan
sodipodi:role="line"
id="tspan4"
style="fill:#000000;stroke-width:0.2"
x="204.76865"
y="256.40359">Freie Testzeit</tspan><tspan
sodipodi:role="line"
style="fill:#000000;stroke-width:0.2"
x="204.76865"
y="256.40359"
id="tspan5" /></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="203.7793"
y="265.51187"
id="text6"><tspan
sodipodi:role="line"
id="tspan6"
style="fill:#000000;stroke-width:0.2"
x="203.7793"
y="265.51187">in Sekunden</tspan></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="191.73494"
y="162.06888"
id="text4-6"><tspan
sodipodi:role="line"
id="tspan4-1"
style="fill:#000000;stroke-width:0.2"
x="191.73494"
y="162.06888">kostenpflichtige</tspan><tspan
sodipodi:role="line"
style="fill:#000000;stroke-width:0.2"
x="191.73494"
y="162.06888"
id="tspan5-8" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:14.8167px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="241.88585"
y="126.91018"
id="text4-6-2"><tspan
sodipodi:role="line"
id="tspan7"
x="241.88585"
y="126.91018">Laser Cutter Zeiterfassung</tspan><tspan
sodipodi:role="line"
id="tspan8"
x="241.88585"
y="126.91018"></tspan></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="199.64789"
y="171.17715"
id="text6-9"><tspan
sodipodi:role="line"
id="tspan6-2"
style="fill:#000000;stroke-width:0.2"
x="199.64789"
y="171.17715">Laserzeit in Minuten</tspan></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#ff0000;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="103.04314"
y="163.84862"
id="text4-6-4"><tspan
sodipodi:role="line"
id="tspan4-1-1"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="103.04314"
y="163.84862">Wifi</tspan><tspan
sodipodi:role="line"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="103.04314"
y="163.84862"
id="tspan5-8-1" /></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#ff0000;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="104.63658"
y="171.17715"
id="text6-9-3"><tspan
sodipodi:role="line"
id="tspan6-2-8"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="104.63658"
y="171.17715">Error</tspan></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#ff0000;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="106.06905"
y="255.2171"
id="text4-6-4-7"><tspan
sodipodi:role="line"
id="tspan4-1-1-9"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="106.06905"
y="255.2171">MQTT</tspan><tspan
sodipodi:role="line"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="106.06905"
y="255.2171"
id="tspan5-8-1-3" /></text>
<text
xml:space="preserve"
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#ff0000;stroke:#ff0000;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
x="104.63658"
y="265.51187"
id="text6-9-3-1"><tspan
sodipodi:role="line"
id="tspan6-2-8-9"
style="fill:#ff0000;stroke-width:0.2;stroke:#ff0000"
x="104.63658"
y="265.51187">Error</tspan></text>
<path
style="fill:none;stroke:#241212;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
d="m 115.50744,249.24178 -0.27313,21.36127 101.41239,-0.15245 -0.0612,-23.82776"
id="path6"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#241212;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
d="m 216,172.81966 -0.13385,-22.4101 -101.04619,-0.15132 0.11406,22.56145"
id="path6-3"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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 03 → akkumulierte Laserzeit (Float, Minuten)
// Zone 1 (bottom): Module 47 → Countdown / Status
// Modul-Aufteilung (oben links = Index 0):
// Modul 0 : WiFi-Fehler-Indikator (showWifiError)
// Modul 13 (oben) : Laserzeit in ganzen Minuten, 3 Stellen rechtsbündig
// Modul 4 : MQTT-Fehler-Indikator (showMqttError)
// Modul 57 (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 <Arduino.h>
@ -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 13 (oben): Laserzeit in ganzen Minuten, 3 Stellen rechtsbündig
// 09 → " x" z.B. " 7"
// 1099 → " xx" z.B. " 42"
// 100999 → "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)
// 0999s werden dargestellt, > 999 → " !!!"
// Modul 57 (unten): Countdown-Sekunden, 3 Stellen rechtsbündig
// 09 → " x"
// 1099 → " xx"
// 100999 → "xxx"
// ≥ 1000 → "!!!"
// Modul 4 bleibt unberührt.
void showCountdown(int seconds);
// Untere Zone: Leerlauf-Anzeige (" --")
// Modul 57 (unten): Leerlauf-Anzeige (" --")
// Modul 4 bleibt unberührt.
void showIdle();
// Untere Zone: Statustext (max. 4 Zeichen, wird abgeschnitten / aufgefüllt)
// Modul 57 (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();

View File

@ -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 13, 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 57, 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 57)
// ---------------------------------------------------------------------------
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 57)
// ---------------------------------------------------------------------------
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");
}

View File

@ -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 <Arduino.h>
#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);
}
}
}