- Add include/laser_tracker.h: LaserTracker class declaration
- Software debounce (LASER_DEBOUNCE_MS = 50ms from config.h)
- Polarity-aware GPIO read (LOW_ACTIVE / HIGH_ACTIVE from settings)
- Session lifecycle: onSessionStart / onSessionEnd
- Gratis time countdown, net seconds accumulation
- Getters: isActive, getTotalMinutes, getSessionSeconds,
getCountdownRemaining, getLastSessionSeconds, resetTotal
- Add src/laser_tracker.cpp: full implementation
- readRaw() applies signal polarity
- loop() handles debounce state machine and edge detection
- onSessionEnd() saves updated totalMinutes to NVS
- getTotalMinutes() returns live value (base + current net session)
- Add test_sketches/test_laser_tracker.cpp: interactive test sketch
- Button on GPIO 4 simulates laser signal
- BOOT button (GPIO 0, 3s hold) resets total
- Display: modules 1-3 = total minutes, modules 5-7 = countdown/net
- Add test-laser-tracker env to platformio.ini
- Update src/main.cpp: integrate laserTracker.begin/loop + live display
- Update Implementation-Plan.md: mark Phase 5 tasks 5.1-5.6 complete
11 KiB
Implementation Plan – Laser Cutter MQTT Display
Dieses Dokument beschreibt die Entwicklungsreihenfolge des Projekts.
Tasks werden während der Entwicklung mit[x]abgehakt.
Phase 1 – Projekt-Setup & Konfiguration
-
1.1
platformio.inimit allen benötigten Bibliotheken erweiternMD_Parola,MD_MAX72XXPubSubClientWiFiManager(tzapu/WiFiManager)ESPAsyncWebServer+AsyncTCPArduinoJsonElegantOTAPreferences(ESP32 built-in, kein Extra-Eintrag nötig)
-
1.2
include/config.herstellen – zentrale Pin-Definitionen und Konstanten- GPIO-Pins (MAX7219 SPI, Laser-Signal)
- Anzahl der Module (8), Zonen-Aufteilung
- Standard-Werte (Gratiszeit 20s, MQTT Port 1883, etc.)
- MQTT-Topic-Konstanten
-
1.3 Build prüfen (leeres Projekt kompiliert fehlerfrei)
-
1.4 Test Verdrahtung Dot-Matrix-Display
- GYMAX7219-Module gemäß Pinbelegung angeschlossen (MOSI GPIO 23, CLK GPIO 18, CS GPIO 5)
- Hardware-Typ
GENERIC_HW(verifiziert; physische 90° Verdrehung per Software CCW kompensiert) - Externes 5 V-Netzteil erforderlich (~0,5 A / ~2,5 W gemessen; USB-Port reicht nicht)
- SPI-Taktrate auf 1 MHz reduziert für stabile Ansteuerung aller 8 Module
- Modul 1 oben-links, Modul 4 oben-rechts, Modul 5 unten-links, Modul 8 unten-rechts
- Zone 0 (0-idx 0–3) = obere Reihe, Zone 1 (0-idx 4–7) = untere Reihe
-
1.5 Test Verdrahtung Potentialfreier Schalter (Phase 1: Push Button)
- Push Button an GPIO 4 und GND angeschlossen (INPUT_PULLUP, kein externer Widerstand nötig)
- Pegel verifiziert: Button offen = HIGH, Button gedrückt = LOW → Polarität: LOW_ACTIVE
- Debounce 50 ms funktioniert
Phase 2 – NVS Persistenz (Settings)
-
2.1
include/settings.h+src/settings.cpperstellen
Speichert und lädt alle Konfigurationswerte über ESP32Preferences(NVS):- MQTT Broker IP, Port, User, Passwort
- Gratiszeit (0–120 s)
- Signal-Polarität (
LOW_ACTIVE/HIGH_ACTIVE) - Akkumulierte Laserzeit (float, Minuten)
- Globale Instanz
settings,settings.begin()inmain.cpp
-
2.2 Unit-Test (Serial-Output): Werte schreiben, ESP32 neu starten, Werte lesen und verifizieren
test_sketches/test_nvs.cpperstellt,test-nvsEnvironment inplatformio.ini- Round-Trip-Test + Persistenz-Check über Neustart
Phase 3 – WiFi & WiFiManager
-
3.1 WiFiManager einbinden
include/wifi_connector.h+src/wifi_connector.cpperstellt- AP-Name:
LaserCutter-Setup(ausconfig.h) - Captive-Portal-Seite für WLAN-Credentials
- Timeout: 120 s, danach
ESP.restart()
-
3.2 Verbindungsstatus-Callback implementieren (für spätere Fehleranzeige)
WifiStatusCallback-Typ +onStatusChange()Methode- Status-Enum:
DISCONNECTED,AP_ACTIVE,CONNECTING,CONNECTED - Globale Instanz
wifiConnector,wifiConnector.begin()+.loop()inmain.cpp
-
3.3 Test: Erstverbindung mit neuem AP, gespeichertes WLAN nach Neustart automatisch verbinden
test_sketches/test_wifi.cpperstellt,test-wifiEnvironment inplatformio.ini- BOOT-Taste (GPIO 0, 3 s) löscht Credentials und erzwingt Portal
- Erstverbindung via Captive Portal ✅, Auto-Reconnect nach Neustart ✅
Phase 4 – Dot-Matrix-Display (DisplayManager)
-
4.1
include/display_manager.h+src/display_manager.cpperstellen
Implementierung mit rohemMD_MAX72XX(nicht MD_Parola) für vollständige Rotationskontrolle:- Zone 0 (Module 0–3, obere Reihe): Laserzeit in Minuten
- Zone 1 (Module 4–7, untere Reihe): Countdown / Statusmeldungen
rotateCCW()kompensiert physische 90°-CW-Verdrehung der Module- 17 Zeichen-Bitmaps definiert (0–9, Sonderzeichen, Buchstaben)
-
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 aktivshowStatus(const char* msg)– max. 4-Zeichen-Statusmeldung (untere Zeile)setBrightness(),allLedsOn(),allLedsOff(),clear(),printToSerial()update()– inloop()aufrufen (no-op, Interface für künftige Animationen)main.cppintegriert:display.begin(),display.showIdle(),display.update()
-
4.3 Test:
test_sketches/test_display_manager.cpp, Environmenttest-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 ✅
Phase 5 – Laser-Signaldetektion & Zeit-Tracking (LaserTracker)
-
5.1
include/laser_tracker.h+src/laser_tracker.cpperstellt- Globale Instanz
laserTracker, inmain.cppintegriert
- Globale Instanz
-
5.2 GPIO-Eingang konfiguriert:
INPUT_PULLUPauf GPIO 4- Software-Debounce 50 ms (
LASER_DEBOUNCE_MSausconfig.h) - Polarität aus
settings.get().signalPolarity(LOW_ACTIVE / HIGH_ACTIVE)
-
5.3 Session-Logik implementiert:
onSessionStart()intern →_sessionActive = true, Timer setzenonSessionEnd()intern → Netto-Sekunden berechnen,_totalMinutesBaseaddieren, NVS viasettings.saveTotalMinutes()
-
5.4 Gratiszeit-Logik:
- Countdown läuft ab Session-Start
getSessionSeconds()gibt erst nach Ablauf der Gratiszeit > 0 zurückgetCountdownRemaining()→ verbleibende Sekunden (0 wenn abgelaufen / inaktiv)getTotalMinutes()= NVS-Basis + laufende Session-Netto-Minuten (Live-Anzeige)
-
5.5 Öffentliche Getter:
getTotalMinutes()→ float (Live)getSessionSeconds()→ int (Netto ohne Gratiszeit)getCountdownRemaining()→ int (verbleibende Gratiszeit)isActive()→ boolgetLastSessionSeconds()→ int (letzte abgeschlossene Session, für MQTT)resetTotal()→ Gesamtzeit auf 0 + NVS-Speicherung
-
5.6 Test:
test_sketches/test_laser_tracker.cpp, Environmenttest-laser-tracker- Button GPIO 4 simuliert Laser-Signal
- BOOT-Taste (GPIO 0, 3 s) löscht Gesamtzeit
- Boot-Output: NVS-Werte geladen, Display zeigt Basiswert + Idle ✅
- Manuelle Verifikation: Button drücken → Countdown, Netto-Zeit, Session-Ende → NVS
Phase 6 – MQTT Client (MqttClient)
-
6.1
include/mqtt_client.h+src/mqtt_client.cpperstellen
Wrapper umPubSubClient -
6.2 Verbindungsaufbau mit Credentials aus
Settings -
6.3 Publish-Methode
publishSession(sessionSec, totalMin, gratisSec):- Topic:
lasercutter/session - JSON mit
session_s,total_min,gratiszeit_s,ts - Wird von
LaserTrackerbeim Session-Ende aufgerufen
- Topic:
-
6.4 Publish-Methode
publishStatus(totalMin, ipStr, uptimeSec):- Topic:
lasercutter/status - Heartbeat alle 60 Sekunden
- QoS 0
- Topic:
-
6.5 Subscribe auf
lasercutter/reset:- Payload
"1"oder{"action":"reset"}→LaserTracker::resetTotal() - QoS 1
- Payload
-
6.6 Reconnect-Logik (Non-blocking, max. alle 10 Sekunden versuchen)
-
6.7 Test: Session-Daten mit MQTT Explorer empfangen, Reset auslösen und verifizieren
Phase 7 – Webinterface (WebServer)
-
7.1
include/web_server.h+src/web_server.cpperstellen
Basierend aufESPAsyncWebServer -
7.2 Route
GET /– Statusseite (HTML):- Aktuelle Laserzeit (Minuten)
- Letzter Session-Wert (Sekunden)
- WLAN-Status, MQTT-Status
- IP-Adresse
-
7.3 Route
GET /config– Konfigurationsformular (HTML):- MQTT Broker IP, Port, User, Passwort
- Gratiszeit (Slider 0–120 s)
- Signal-Polarität (Radio-Button)
-
7.4 Route
POST /config– Konfiguration speichern (viaSettings, NVS) -
7.5 Route
POST /reset– Laserzeit zurücksetzen (Button auf Statusseite) -
7.6 ElegantOTA unter
/updateeinbinden -
7.7 Test: Alle Seiten im Browser aufrufen, Konfiguration ändern und nach Neustart prüfen
Phase 8 – Integration & Hauptprogramm (main.cpp)
-
8.1
main.cppaufräumen und alle Module initialisieren:- Reihenfolge:
Settings→DisplayManager→WiFiManager→MqttClient→WebServer→LaserTracker
- Reihenfolge:
-
8.2
loop()implementieren:displayManager.update()laserTracker.update()(GPIO-Polling + Session-Logik)mqttClient.loop()(Reconnect + Heartbeat)displayManager.showLaserTime(laserTracker.getTotalMinutes())displayManager.showCountdown(...)odershowIdle()odershowError(...)
-
8.3 Callback von
LaserTracker→MqttClient.publishSession(...)verdrahten -
8.4 WLAN/MQTT-Fehlerzustand auf Display spiegeln
-
8.5 Watchdog Timer aktivieren (ESP32 WDT, 30 s)
Phase 9 – OTA & Stabilisierung
-
9.1 ElegantOTA in
main.cppinitialisieren und inloop()einbinden -
9.2 Serielles Logging vereinheitlichen (Log-Level: INFO / DEBUG per
#define) -
9.3 Langzeit-Test: 24h Betrieb, Speicherleck-Check via Serial Monitor (
ESP.getFreeHeap()) -
9.4 Edge-Cases testen:
- Laser aktiv, WLAN wird getrennt → Display läuft weiter
- MQTT-Broker nicht erreichbar → Reconnect
- Reset während aktiver Session
- Neustart nach akkumulierter Zeit → Zeit korrekt aus NVS geladen
Phase 10 – Dokumentation & Abschluss
-
10.1 README.md finalisieren (Screenshots, Schaltplan-Skizze ggf. ergänzen)
-
10.2
platformio.inimit finalen Library-Versionen dokumentieren -
10.3 MQTT-Topics in README.md validieren (mit realem Broker testen)
-
10.4 Optionaler Schaltplan (Fritzing / ASCII) für Laser-Signal-Anschluss (Optokoppler)
Abhängigkeitsdiagramm (Reihenfolge)
Phase 1 (Setup)
└── Phase 2 (NVS/Settings)
├── Phase 3 (WiFi)
├── Phase 4 (Display)
└── Phase 5 (LaserTracker)
└── Phase 6 (MQTT)
└── Phase 7 (Webinterface)
└── Phase 8 (Integration)
└── Phase 9 (OTA/Stabilisierung)
└── Phase 10 (Doku)
Verwendete MQTT-Topics (Überblick)
| Topic | Richtung | QoS | Auslöser |
|---|---|---|---|
lasercutter/session |
Publish | 1 | Session-Ende (Laser inaktiv) |
lasercutter/status |
Publish | 0 | Heartbeat alle 60 s |
lasercutter/reset |
Subscribe | 1 | Externer Reset-Befehl |
Erstellt: 22. Februar 2026