Laser Cutter Dot Matrix Display und MQTT Client
Go to file
2026-02-22 21:20:06 +01:00
.github chore: Copilot Workspace Instructions hinzugefuegt (Deutsch, Terminal-Wiederverwendung) 2026-02-22 19:45:24 +01:00
.vscode Initial Version of README.md 2026-02-22 10:34:37 +01:00
doc Update README.md Screenshots 2026-02-22 21:20:06 +01:00
include feat(web): Phase 7 WebServer (ESPAsyncWebServer, Config, Reset, ElegantOTA) 2026-02-22 20:43:08 +01:00
lib Initial Version of README.md 2026-02-22 10:34:37 +01:00
parser 1. Vorschlag Implementierung 2026-02-22 11:13:16 +01:00
src fix(web): POST /reset setzt nur Session zurueck (resetSession), nicht NVS-Gesamtzeit 2026-02-22 20:55:24 +01:00
test Initial Version of README.md 2026-02-22 10:34:37 +01:00
test_sketches fix(web): POST /reset setzt nur Session zurueck (resetSession), nicht NVS-Gesamtzeit 2026-02-22 20:55:24 +01:00
.gitignore feat(mqtt): TLS-Unterstuetzung (WiFiClientSecure, Port 8883), Secrets-Datei gitignoriert 2026-02-22 20:06:53 +01:00
Implementation-Plan.md fix(web): POST /reset setzt nur Session zurueck (resetSession), nicht NVS-Gesamtzeit 2026-02-22 20:55:24 +01:00
platformio.ini feat(web): Phase 7 WebServer (ESPAsyncWebServer, Config, Reset, ElegantOTA) 2026-02-22 20:43:08 +01:00
README.md Update README.md Screenshots 2026-02-22 21:20:06 +01:00

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

  1. Hardware
  2. Pinbelegung
  3. Display
  4. Laser-Signaldetektion
  5. Zeit-Tracking & Gratiszeit
  6. MQTT Client
  7. Webinterface
  8. Konfiguration & Persistenz
  9. WiFi-Setup (WiFiManager)
  10. OTA Firmware-Update
  11. Fehlerverhalten
  12. Bibliotheken
  13. Build & Flash
  14. Tests
  15. Beitragen / Commits

Skizze und Screenshots

Front-Ansicht des Laser Cutter Displays

Das Bild zeigt die physische Anordnung der 8 GYMAX7219-Module im 4×2-Format mit LED-Positionen und Beschriftung der Anzeigebereiche.

LaserCutter Display

Konfiguration

OTA Firmware-Update

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_ACTIVE oder HIGH_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: 03
 │ oben    │         │         │ oben    │    Anzeige: Laserzeit (Minuten)
 │ links   │         │         │ rechts  │
 ├─────────┼─────────┼─────────┼─────────┤
 │ Modul 5 │ Modul 6 │ Modul 7 │ Modul 8 │  ← Zone 1 (unten)  0-idx: 47
 │ 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)
13 13 Oben Mitterechts Session-Minuten ganzzahlig (0 bei Neustart, RAM-only)
4 4 Unten links MQTT-Fehler (E = kein Broker, leer = OK)
57 57 Unten Mitterechts Countdown (Gratiszeit in Sek.) oder -- (Idle/Netto)
  • Session-Minuten (Module 13) inkrementierten erst nach vollen 60 Netto-Sekunden (harte Ganzzahl).
  • Die Netto-Zeit beginnt erst nach Ablauf der Gratiszeit (Laser muss länger als gratisSeconds aktiv 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 LOW oder HIGH den 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 13
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: 0120 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, 0120
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
│   ├── mqtt_client.cpp       # MQTT-Wrapper (PubSubClient, TLS, Phase 6)
│   └── web_server.cpp        # Webinterface (ESPAsyncWebServer, Phase 7)
├── 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
│   ├── test_mqtt_client.cpp      # 6.5 - MqttClient (TLS, Session, Heartbeat)
│   ├── test_web_server.cpp       # 7.7 - WebServer (Routen, Config, OTA)
│   └── mqtt_test_secrets.h       # (gitignoriert) persönliche Broker-Credentials
├── 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
6.5 test-mqtt test_mqtt_client.cpp MqttClient TLS, Heartbeat, Session-Publish, JSON-Reset, LWT
7.7 test-web test_web_server.cpp WebServer Routen, Config-Formular, Reset, ElegantOTA OTA

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 0
  • showLaserTime(n) → Module 13 (ganze Minuten: 0, 1, 42, 999)
  • showMqttError(true/false) → Modul 4
  • showCountdown(n) → Module 57
  • showIdle() → Module 57 (--)

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 13 Module 57 Serial
Idle 0 --
Laser an (020 s) 0 Countdown 201 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 haltensettings.reset() + resetTotal() → alle NVS-Werte auf Default, Session = 0.

Test 6.5 MqttClient (Phase 6)

⚠️ Voraussetzung: test_sketches/mqtt_test_secrets.h anlegen (Vorlage: mqtt_test_secrets.h.example)

pio run -e test-mqtt --target upload
pio device monitor -e test-mqtt

Erwartetes Verhalten:

Aktion Serial / MQTT Explorer
Start Verbindung zu Broker, lasercutter/status retained {"online":true,...}
Alle 30 s Heartbeat auf lasercutter/status mit uptime_s
Laser AN/AUS (>20 s) Session-Publish auf lasercutter/session
{"reset":true} senden an lasercutter/reset resetTotal() → Session = 0
ESP32 stromlos LWT {"online":false} erscheint nach Keep-Alive-Timeout
Modul 4 E blinkt bei MQTT-Verbindungsverlust

BOOT-Taste (GPIO 0) 3 s haltenresetTotal() → Gesamtzeit auf 0.

Status: Verbindung, Heartbeat, Session-Publish, JSON-Reset, LWT getestet
⚠️ Ungetestet: MQTT-Error Modul 5 bei Verbindungsverlust, Reconnect nach WiFi-Ausfall

Test 7.7 WebServer (Phase 7)

pio run -e test-web --target upload
pio device monitor -e test-web

IP-Adresse aus Serial Monitor ablesen, dann im Browser:

URL Erwartetes Verhalten
http://<IP>/ Statusseite: Session, Gesamtzeit (NVS), WLAN-IP, MQTT-Status, Broker
http://<IP>/config Formular: Broker, Port, User, PW, Gratiszeit, Polarität
POST /config Werte in NVS speichern, nach Neustart prüfen
Button „Session zurücksetzen“ resetSession() → Session = 0 min, Gesamtzeit (NVS) bleibt erhalten
http://<IP>/update ElegantOTA-Seite erreichbar

Statusseite aktualisiert sich automatisch alle 10 s.

Status: Hardware-Test ausstehend


Implementierungsstand

Phase Beschreibung Status
1 Hardware-Grundtest (Display, Button) abgeschlossen
2 NVS-Persistenz (Settings) abgeschlossen
3 WiFiManager abgeschlossen
4 DisplayManager abgeschlossen
5 LaserTracker (Burst-Logik, Zeiterfassung) abgeschlossen
6 MqttClient (PubSubClient, TLS, Heartbeat, Session) implementiert / ⚠️ 2 Punkte ungetestet
7 WebServer (ESPAsyncWebServer, Config-UI, OTA) implementiert / Hardware-Test ausstehend
8 Integrationstest (Display + MQTT + Web) ausstehend
9 Gehäuse / Hardware-Finish ausstehend
10 Produktiv-Deployment, Dokumentation final ausstehend

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.