| .github | ||
| .vscode | ||
| doc | ||
| include | ||
| lib | ||
| parser | ||
| src | ||
| test | ||
| test_sketches | ||
| .gitignore | ||
| Implementation-Plan.md | ||
| platformio.ini | ||
| README.md | ||
Laser Cutter Dot Matrix Display und MQTT Client
Dieses Projekt implementiert einen ESP32-basierten MQTT-Client mit Dot-Matrix-Display für einen Laser Cutter. Es misst und visualisiert die aktive Laserzeit, sendet Session-Daten an einen MQTT-Broker und bietet ein Browser-Webinterface zur Konfiguration und Steuerung.
Inhaltsverzeichnis
- Hardware
- Pinbelegung
- Display
- Laser-Signaldetektion
- Zeit-Tracking & Gratiszeit
- MQTT Client
- Webinterface
- Konfiguration & Persistenz
- WiFi-Setup (WiFiManager)
- OTA Firmware-Update
- Fehlerverhalten
- Bibliotheken
- Build & Flash
- Tests
- Beitragen / Commits
Hardware
| Komponente | Modell / Beschreibung |
|---|---|
| Mikrocontroller | AZ-Delivery ESP32 DevKit V4 |
| Dot-Matrix-Display | 8× GYMAX7219 Module (kompatibel zu MAX7219), 8×8 LEDs je Modul, Typ: GENERIC_HW |
| Display-Anordnung | 4 Module nebeneinander × 2 Reihen = 32×16 LEDs |
| Display-Kaskadierung | Alle 8 Module in einer einzigen SPI-Kette |
| Stromversorgung Display | Externes 5 V-Netzteil erforderlich (gemessen: ~0,5 A / ~2,5 W bei 8 Modulen, Vollast bis ~2 A möglich) – ESP32 USB-Port reicht nicht aus |
| Laser-Aktivsignal | Potentialfreier Ausgang des Laser Cutters (Optokoppler empfohlen) |
| Optional | Shelly PM Mini G3 als externer Leistungszähler (separater MQTT-Service) |
Pinbelegung
| Signal | ESP32 GPIO | Beschreibung |
|---|---|---|
| MAX7219 MOSI | GPIO 23 | SPI Data (VSPI) → DIN Modul 1 |
| MAX7219 CLK | GPIO 18 | SPI Clock (VSPI) |
| MAX7219 CS | GPIO 5 | SPI Chip Select (alle Module) |
| Laser-Signal | GPIO 4 | Potentialfreier Eingang (INPUT_PULLUP, konfigurierbar) |
Die Polarität des Laser-Signals (
LOW_ACTIVEoderHIGH_ACTIVE) ist über das Webinterface konfigurierbar.
SPI-Ketten-Schaltbild
ESP32 OBERE REIHE UNTERE REIHE
GPIO 23 (MOSI) ─── DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐
│ M 1 │ │ M 2 │ │ M 3 │ │ M 4 │
│oben │ │ │ │ │ │oben │
│links │ │ │ │ │ │rechts│
GPIO 18 (CLK) ─────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──
GPIO 5 (CS) ─────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──
└──────┘ └──────┘ └──────┘ └──┬───┘
│ DOUT
┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──┴───┐
│ M 8 │ │ M 7 │ │ M 6 │ │ M 5 │
│unten │ │ │ │ │ │unten │
│rechts│ │ │ │ │ │links │
GPIO 18 (CLK) ─────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK │
GPIO 5 (CS) ─────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──────────┤ CS │
└──────┘ └──────┘ └──────┘ └──────┘
Hinweis: Die untere Reihe läuft im Schaltbild von rechts nach links, weil DOUT von Modul 4 direkt zu DIN von Modul 5 geführt wird. MD_Parola berücksichtigt das automatisch.
Display
Das Display besteht aus 8 GYMAX7219-Modulen in einer 4×2-Anordnung (32×16 LEDs gesamt).
Es ist in zwei Zonen aufgeteilt, die unabhängig voneinander beschrieben werden.
Physisches Layout
DIN ← ESP32 GPIO 23
↓
┌─────────┬─────────┬─────────┬─────────┐
│ Modul 1 │ Modul 2 │ Modul 3 │ Modul 4 │ ← Zone 0 (oben) 0-idx: 0–3
│ oben │ │ │ oben │ Anzeige: Laserzeit (Minuten)
│ links │ │ │ rechts │
├─────────┼─────────┼─────────┼─────────┤
│ Modul 5 │ Modul 6 │ Modul 7 │ Modul 8 │ ← Zone 1 (unten) 0-idx: 4–7
│ unten │ │ │ unten │ Anzeige: Countdown / Status
│ links │ │ │ rechts │
└─────────┴─────────┴─────────┴─────────┘
SPI-Kette: ESP32 → M1 → M2 → M3 → M4 → M5 → M6 → M7 → M8
Hardware-Typ: GENERIC_HW (verifiziert durch Hardware-Test; physische Ausrichtung erfordert 90° CCW Software-Rotation)
Stromversorgung: Die 8 Module müssen über ein externes 5 V-Netzteil versorgt werden. GND des Netzteils mit ESP32-GND verbinden. Gemessene Leistungsaufnahme im Betrieb: ca. 0,5 A / 2,5 W; bei allen LEDs EIN (Testmuster) bis ca. 2 A möglich.
Modul-Belegung
| Modul(e) | 0-Index | Physisch | Anzeige-Inhalt |
|---|---|---|---|
| 0 | 0 | Oben links | WiFi-Fehler (E = kein WLAN, leer = OK) |
| 1–3 | 1–3 | Oben Mitte–rechts | Session-Minuten ganzzahlig (0 bei Neustart, RAM-only) |
| 4 | 4 | Unten links | MQTT-Fehler (E = kein Broker, leer = OK) |
| 5–7 | 5–7 | Unten Mitte–rechts | Countdown (Gratiszeit in Sek.) oder -- (Idle/Netto) |
- Session-Minuten (Module 1–3) inkrementierten erst nach vollen 60 Netto-Sekunden (harte Ganzzahl).
- Die Netto-Zeit beginnt erst nach Ablauf der Gratiszeit (Laser muss länger als
gratisSecondsaktiv bleiben). - Die Anzeige aktualisiert sich im
loop()ohne Blocking-Delays. - Bibliotheken:
MD_MAX72XX(direkte Puffer-Steuerung, keine MD_Parola)
Laser-Signaldetektion
Der ESP32 überwacht einen digitalen GPIO-Eingang mit internem Pull-Up-Widerstand (INPUT_PULLUP).
Der potentialfreie Ausgang des Laser Cutters (z.B. Schließer-Kontakt über Optokoppler) wird an diesen Pin angeschlossen.
- Polarität konfigurierbar: Im Webinterface einstellbar, ob
LOWoderHIGHden aktiven Zustand bedeutet. - Debounce: Software-Entprellung, um Fehlmessungen bei Schaltflanken zu vermeiden.
- Eine Session beginnt, wenn der Laser aktiv wird, und endet, wenn er inaktiv wird.
Zeit-Tracking & Gratiszeit
Zwei getrennte Zeitkreise
| Kreis | Speicher | Reset | Verwendung |
|---|---|---|---|
| Session-Minuten | RAM | bei Neustart / resetSession() |
Display Module 1–3 |
| Gesamtzeit | NVS (total_min) |
nur per resetTotal() |
MQTT, Web-UI, Wartungsstatistik |
Burst-Zustandsmaschine
Jedes Laser-AN-Ereignis durchläuft drei Zustände:
INACTIVE ──(Laser an)──► GRATIS ──(Gratiszeit abgelaufen)──► NET_COUNTING
▲ │ │
└───────────────────────┴──────────(Laser aus)─────────────────┘
| Zustand | Display unten | Netto-Zeit | NVS |
|---|---|---|---|
INACTIVE |
-- |
– | – |
GRATIS |
Countdown (z.B. 19, 18, ...) |
läuft nicht | – |
NET_COUNTING |
-- |
läuft | – |
→ Laser aus (aus NET_COUNTING) |
-- |
wird addiert | gespeichert |
→ Laser aus (aus GRATIS) |
-- |
0 addiert | Burst-Dauer gespeichert |
Gratiszeit
- Konfigurierbar: 0–120 Sekunden (Standard: 20 s)
- Startet neu bei jedem Laser-AN-Ereignis
- Geht der Laser während der Gratiszeit wieder aus: keine Session-Zeit, aber NVS zählt die Einschaltdauer
- Einstellbar über das Webinterface
NVS-Gesamtzeit
Zählt jede Sekunde Laser-AN, inklusive Gratiszeit. Eignet sich für Wartungsintervalle (tatsächliche Einschaltdauer des Lasers).
Session-Minuten (Display)
Zählt nur die Netto-Zeit (nach Ablauf der Gratiszeit). Inkrementiert hart bei 60 / 120 / 180 ... Netto-Sekunden. Wird bei Neustart und resetSession() auf 0 zurückgesetzt.
MQTT Client
Der ESP32 verbindet sich mit einem MQTT-Broker (konfigurierbar über Webinterface).
Topics
| Richtung | Topic | Format | Beschreibung |
|---|---|---|---|
| Publish | lasercutter/session |
JSON | Wird beim Ende eines Laser-Bursts gesendet |
| Publish | lasercutter/status |
JSON (retained) | Heartbeat alle 60 Sekunden + LWT (online/offline) |
| Subscribe | lasercutter/reset |
{"reset":true} oder "1" |
Setzt die akkumulierte Laserzeit auf 0 |
JSON-Format lasercutter/session
{
"session_s": 125,
"total_min": "42.50",
"gratiszeit_s": 20,
"ip": "192.168.1.100"
}
JSON-Format lasercutter/status
{
"online": true,
"total_min": "42.50",
"ip": "192.168.1.100",
"uptime_s": 3600
}
LWT (Last Will and Testament)
Bei Verbindungsabbruch sendet der Broker automatisch auf lasercutter/status:
{"online": false}
Verhalten
- Non-Blocking Reconnect alle 10 s, falls MQTT-Broker nicht erreichbar.
- Bei aktivem Laser läuft die Zeitmessung unabhängig vom MQTT-Status weiter.
- Client-ID:
lasercutter-display-XXXXXX(mit MAC-Suffix, eindeutig auf Public Broker). - TLS (Port 8883): wird automatisch aktiviert wenn Port 8883 konfiguriert ist (
WiFiClientSecure,setInsecure).
Webinterface
Das Webinterface ist über die IP-Adresse des ESP32 im Browser erreichbar.
| Seite | URL | Funktion |
|---|---|---|
| Status | / |
Aktuelle Laserzeit, letzter Session-Wert, Systemstatus |
| Konfiguration | /config |
MQTT-Broker (IP, Port, User, Passwort), Gratiszeit, Polarität |
| Reset | /reset |
Setzt akkumulierte Laserzeit zurück |
| OTA Update | /update |
Firmware-Update über Browser |
Konfiguration & Persistenz
Alle Einstellungen werden im NVS (Non-Volatile Storage) des ESP32 gespeichert und sind über das Webinterface änderbar:
| Einstellung | Standard | Beschreibung |
|---|---|---|
| MQTT Broker | broker.hivemq.com |
Hostname oder IP des MQTT-Brokers |
| MQTT Port | 1883 |
Port des MQTT-Brokers (8883 = TLS auto) |
| MQTT User | (leer) | MQTT Benutzername (optional) |
| MQTT Passwort | (leer) | MQTT Passwort (optional) |
| Gratiszeit | 20 |
Sekunden, 0–120 |
| Signal-Polarität | LOW_ACTIVE |
LOW_ACTIVE oder HIGH_ACTIVE |
| Akkumulierte Zeit | 0.0 |
Gespeicherte Laserzeit in Minuten (NVS) |
WiFi-Setup (WiFiManager)
Beim ersten Start (oder wenn keine gespeicherten WLAN-Daten vorhanden) öffnet das Gerät einen Access Point mit Captive Portal:
- SSID:
LaserCutter-Setup - Im Browser öffnet sich automatisch die Konfigurationsseite.
- WLAN-Credentials werden nach erfolgreicher Verbindung im NVS gespeichert.
OTA Firmware-Update
Firmware-Updates können kabellos über die Weboberfläche unter /update eingespielt werden (ElegantOTA).
Fehlerverhalten
| Zustand | Modul 0 (oben links) | Modul 4 (unten links) | Verhalten |
|---|---|---|---|
| WLAN getrennt | E (blinkt) |
– | Zeiterfassung läuft weiter, Reconnect |
| MQTT nicht erreichbar | – | E (blinkt) |
Zeiterfassung läuft weiter, Reconnect |
| WLAN + MQTT OK | leer | leer | Normaler Betrieb |
Bibliotheken
| Bibliothek | Zweck |
|---|---|
MD_MAX72XX |
Treiber für MAX7219/GYMAX7219, direkte Puffer-Steuerung |
MD_Parola |
(Dependency von MD_MAX72XX, nicht direkt genutzt) |
PubSubClient |
MQTT Client |
WiFiManager |
WiFi Captive Portal |
ESPAsyncWebServer |
Asynchroner Webserver |
AsyncTCP |
TCP-Basis für ESPAsyncWebServer |
ArduinoJson |
JSON Serialisierung/Deserialisierung |
Preferences |
NVS-Zugriff (built-in ESP32 Arduino) |
ElegantOTA |
OTA-Update über Webinterface |
Build & Flash
# Haupt-Firmware bauen und flashen
pio run -e az-delivery-devkit-v4 --target upload
# Serieller Monitor
pio device monitor
Ziel-Board: az-delivery-devkit-v4 (ESP32), Upload-Port: COM3
Projektstruktur
MQTT-Display-LaserCutter/
├── src/
│ ├── main.cpp # Hauptprogramm
│ ├── display_manager.cpp # Display-Implementierung (MD_MAX72XX)
│ ├── laser_tracker.cpp # Signal-Detektion, Burst-Logik, Zeiterfassung
│ ├── settings.cpp # NVS-Persistenz (Preferences)
│ └── wifi_connector.cpp # WiFiManager-Wrapper
├── include/
│ ├── config.h # Pin-Definitionen, Konstanten
│ ├── display_manager.h # Display-API (showLaserTime, showCountdown, ...)
│ ├── laser_tracker.h # BurstState-Maschine, getSessionMinutes(), ...
│ ├── settings.h # Settings-Struct, SettingsManager
│ ├── wifi_connector.h # WiFi-Verbindungsmanagement
│ ├── mqtt_client.h # (Phase 6) MQTT-Wrapper (PubSubClient)
│ └── web_server.h # (Phase 7) Webinterface (ESPAsyncWebServer)
├── test_sketches/
│ ├── test_display.cpp # 1.4 - GYMAX7219 Moduldignose
│ ├── test_button.cpp # 1.5 - Potentialfreier Schalter
│ ├── test_nvs.cpp # 2.2 - NVS Persistenz
│ ├── test_wifi.cpp # 3.3 - WiFiManager
│ ├── test_display_manager.cpp # 4.3 - DisplayManager
│ └── test_laser_tracker.cpp # 5.6 - LaserTracker
├── platformio.ini
└── README.md
Tests
Alle Tests sind Hardware-Tests (kein Unit-Test-Framework). Sie werden als separate PlatformIO-Environments geflasht und über den Serial Monitor beobachtet.
Übersicht
| Nr. | Environment | Datei | Testet |
|---|---|---|---|
| 1.4 | test-display |
test_display.cpp |
GYMAX7219 Verkabelung, Modul-Nummerierung, Rotation |
| 1.5 | test-button |
test_button.cpp |
Potentialfreier Schalter / Debounce an GPIO 4 |
| 2.2 | test-nvs |
test_nvs.cpp |
NVS Lesen/Schreiben/Reset (SettingsManager) |
| 3.3 | test-wifi |
test_wifi.cpp |
WiFiManager Captive Portal, WLAN-Verbindung |
| 4.3 | test-display-mgr |
test_display_manager.cpp |
DisplayManager API (alle show*-Methoden) |
| 5.6 | test-laser-tracker |
test_laser_tracker.cpp |
LaserTracker Burst-Logik, Gratiszeit, Session/NVS |
Test 1.4 – Display Verdrahtungstest
pio run -e test-display --target upload
pio device monitor -e test-display
Erwartetes Verhalten: Alle 8 Module zeigen nacheinander ihre Nummer, danach Laufschrift und Fülltest.
Test 1.5 – Button / Laser-Eingang
pio run -e test-button --target upload
pio device monitor -e test-button
Erwartetes Verhalten: Serial-Ausgabe zeigt HIGH/LOW beim Betätigen des Schalters an GPIO 4.
Test 2.2 – NVS Persistenz
pio run -e test-nvs --target upload
pio device monitor -e test-nvs
Erwartetes Verhalten: Schreibt Testwerte in NVS, liest sie zurück, prüft Übereinstimmung. Nach Neustart müssen die Werte erhalten bleiben. Alle Tests als PASS im Serial Monitor.
Test 3.3 – WiFiManager
pio run -e test-wifi --target upload
pio device monitor -e test-wifi
Erwartetes Verhalten: Beim ersten Flash öffnet der ESP32 den AP LaserCutter-Setup. Nach Eingabe der WLAN-Credentials verbindet er sich und gibt die IP-Adresse aus. BOOT-Taste (GPIO 0) beim Start 3 s halten löscht gespeicherte Credentials.
Test 4.3 – DisplayManager
pio run -e test-display-mgr --target upload
pio device monitor -e test-display-mgr
Erwartetes Verhalten: Durchläuft automatisch alle show*-Methoden:
showWifiError(true/false)→ Modul 0showLaserTime(n)→ Module 1–3 (ganze Minuten: 0, 1, 42, 999)showMqttError(true/false)→ Modul 4showCountdown(n)→ Module 5–7showIdle()→ Module 5–7 (--)
Test 5.6 – LaserTracker
pio run -e test-laser-tracker --target upload
pio device monitor -e test-laser-tracker
GPIO 4 Schalter betätigen = Laser-AN simulieren.
Erwartetes Verhalten:
| Aktion | Module 1–3 | Module 5–7 | Serial |
|---|---|---|---|
| Idle | 0 |
-- |
– |
| Laser an (0–20 s) | 0 |
Countdown 20→1 |
BurstStart -> GRATIS |
| Laser an (>20 s) | 0 |
-- |
GRATIS abgelaufen -> NET_COUNTING |
| Laser aus (nach 80 s netto) | 1 |
-- |
BurstEnd: gesamt=... netto=... |
| Nach 60 weiteren Netto-Sek. | 2 |
-- |
– |
BOOT-Taste (GPIO 0) 3 s halten → settings.reset() + resetTotal() → alle NVS-Werte auf Default, Session = 0.
Beitragen / Commits
Dieses Projekt verwendet Conventional Commits für alle Git-Commit-Nachrichten.
Format
<type>(<scope>): <kurze Beschreibung>
[optionaler Body]
[optionaler Footer]
Wichtigste Typen
| Typ | Verwendung |
|---|---|
feat |
Neues Feature |
fix |
Bugfix |
docs |
Nur Dokumentation |
refactor |
Code-Umstrukturierung ohne Feature/Fix |
test |
Test-Sketches / Test-Code |
chore |
Build-System, Abhängigkeiten, Konfiguration |
Beispiele
feat(display): add GENERIC_HW rotation compensation
fix(nvs): prevent crash on empty broker string
docs(readme): add power supply requirements
chore(platformio): reduce SPI clock to 1 MHz for stability
Breaking Changes werden mit ! nach dem Typ markiert oder im Footer mit BREAKING CHANGE: beschrieben.