MQTT-Display-LaserCutter/README.md
2026-02-26 18:50:45 +01:00

22 KiB
Raw Blame History

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

LaserCutter Display

[X] Ändere den Begriff "Session" in "aktuelle Laserzeit" (das ist die aktuelle Session) [X] Letzer Burst entspricht "aktueller Laserzeit". Dieser Begriff "Burst" kann weg. [X] Ergänze "Summe Laserzeit" (Das ist die aktuelle Summer aller Session bis zu einem manuellen Reset über das Web, MQTT, Button oder Restart) [X] Ersetze "Gesamtzeit (NVS)" durch "Maschinenlaufzeit (gesamt)"

Konfiguration

Konfiguration

OTA Firmware-Update Seite

OTA Firmware-Update

Hardware

Komponente Modell / Beschreibung
Mikrocontroller AZ-Delivery ESP32 DevKit V4 EPS32-WROOM (Dual Core)
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    │    idx 0    -> Anzeige: WiFi-Fehler (E = Fehler, leer = OK)
 │ links   │         │         │ rechts  │    idx 1 -3 -> Anzeige: Summe Laserzeit (Minuten) -> Summe aller Sessions bis zum Reset
 ├─────────┼─────────┼─────────┼─────────┤
 │ Modul 5 │ Modul 6 │ Modul 7 │ Modul 8 │  ← Zone 1 (unten)  0-idx: 47
 │ unten   │         │         │ unten   │    idx 4    -> Anzeige: MQTT-Fehler (E = kein Broker, leer = OK)
 │ links   │         │         │ rechts  │    idx 5 -7 -> Anzeige: Countdown / Status 
 └─────────┴─────────┴─────────┴─────────┘
  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)
  • Summe Session (Module 13) inkrementiert mit jeder angefangenen Minute pro Session (Ceiling). Beispiel: 10 s Netto = +1 Min, 65 s Netto = +2 Min (1 bei Sekunde 1, 2 ab Sekunde 61).
  • 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. [TODO] Parameter für Debounce-Zeit (z.B. 50 ms) konfigurierbar machen.
  • Eine Session beginnt, wenn der Laser aktiv wird, und endet, wenn er inaktiv wird.
  • Eine Session besteht aus Abglaufener Gratizeit + Laser-AN-Zeit (Netto-Zeit). Eine "Session" ohne Laser Zeit ist keine Session und wird auch nicht als MQTT Session veröffentlicht. Unabhängig von einer vollständigen Session wird die Laser-AN-Zeit immer in der NVS-Gesamtzeit akkumuliert (für Wartungsintervalle).

Zeit-Tracking & Gratiszeit

Drei getrennte Zeitkreise

Kreis Speicher Reset Verwendung
Session-Minuten RAM bei Neustart / resetSessionSum() MQTT Session in Minuten, Web-UI
Summe Session RAM bei Neustart / resetSessionSum() Display Module 13, Web-UI, nicht in MQTT
Gesamtzeit NVS (total_min) nur per resetTotal() MQTT, Web-UI, Wartungsstatistik

Session-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 Sekundenzähler siehe Session-Sekunden läuft
→ Laser aus (aus NET_COUNTING) -- wird addiert gespeichert
→ Laser aus (aus GRATIS) -- 0 addiert Dauer verwendeter Gratiszeit wird 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). Jede angefangene Minute pro Session wird aufgerundet gezählt (Ceiling). Beispiele:

  • Session mit 10 s Netto → +1 Minute
  • Session mit 65 s Netto → live +1 bei Sekunde 160, dann +2 ab Sekunde 61
  • Session mit 120 s Netto → live +1 bei Sek. 160, +2 ab Sek. 61120

Wird bei Neustart und resetSessionSum() auf 0 zurückgesetzt.

Session-Sekunden

Dies wird in Form eines umlaufenden Kreis um die Module 57 angezeigt wie es auf einer Analogen Uhr überlich ist. 3 Module haben umlaufenden 60 Leds. Jede Sekunden wird eine weitere LED dazu geschaltet. Nach 60 Sekunden ist der Kreis voll und die Minutenanzeige (Module 13) wird inkrementiert. Bei Laser-Aus wird diese Anzeige wieder auf "--" 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 oder nach Session-Reset gesendet
Publish lasercutter/status JSON (retained) Heartbeat alle 60 Sekunden + LWT (online/offline)
Subscribe lasercutter/reset {"reset":true} oder "1" Setzt die gesamte akkumulierte Laserzeit (NVS + RAM) auf 0
Subscribe lasercutter/reset {"reset_session":true} Setzt nur die Session-Summe (RAM) auf 0, NVS bleibt erhalten

JSON-Format lasercutter/session

{
  "session_minutes": 2,
  "session_seconds": 95,
  "session_start_time": "2026-02-23T12:34:56Z",
  "freetime_s": 20,
  "ip": "192.168.2.62"
}

session_start_time ist ein UTC-Zeitstempel (ISO 8601), synchronisiert via NTP (pool.ntp.org) unmittelbar nach WLAN-Connect.
Wert "unknown" wenn die NTP-Synchronisation beim Session-Start noch nicht abgeschlossen war.

Bei einem Session-Reset (Web oder MQTT {"reset_session":true}) wird ebenfalls ein lasercutter/session-Publish ausgelöst mit den bis dahin akkumulierten Werten identisches Format wie beim normalen Session-Ende.

JSON-Format lasercutter/status

{
  "online": true,
  "session_sum": "42.50",
  "machine_running_time_min": "1234.75",
  "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, Web-Passwort
Reset /reset Setzt die Session-Summe (RAM) auf 0 zurück NVS-Gesamtzeit bleibt erhalten
OTA Update /update Firmware-Update über Browser

Alle Seiten sind per HTTP Basic Auth geschützt, wenn in der Konfiguration ein Web-Passwort gesetzt ist.


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)
Web-Benutzername admin HTTP-Basic-Auth Benutzername
Web-Passwort (leer) Leer = kein Passwortschutz; setzen über /config

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       # SessionState-Maschine, getAllSessionsSumMinutes(), ...
│   ├── 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

Die tests sind in test_sketches/ als eigenständige Sketches organisiert, die jeweils spezifische Funktionalitäten der Hardware oder Software testen. Auch die Beschreibung der tests befindet sich in der README.md in dem Verzeichneis test_sketches/ um die Übersicht zu behalten.


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, Reset) abgeschlossen
7 WebServer (ESPAsyncWebServer, Config-UI, Auth, OTA) abgeschlossen
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.