MQTT-Display-LaserCutter/README.md
2026-02-22 21:20:06 +01:00

541 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#hardware)
2. [Pinbelegung](#pinbelegung)
3. [Display](#display)
4. [Laser-Signaldetektion](#laser-signaldetektion)
5. [Zeit-Tracking & Gratiszeit](#zeit-tracking--gratiszeit)
6. [MQTT Client](#mqtt-client)
7. [Webinterface](#webinterface)
8. [Konfiguration & Persistenz](#konfiguration--persistenz)
9. [WiFi-Setup (WiFiManager)](#wifi-setup-wifimanager)
10. [OTA Firmware-Update](#ota-firmware-update)
11. [Fehlerverhalten](#fehlerverhalten)
12. [Bibliotheken](#bibliotheken)
13. [Build & Flash](#build--flash)
14. [Tests](#tests)
15. [Beitragen / Commits](#beitragen--commits)
---
## Skizze und Screenshots
![Front-Ansicht des Laser Cutter Displays](./doc/Front.svg)
Das Bild zeigt die physische Anordnung der 8 GYMAX7219-Module im 4×2-Format mit LED-Positionen und Beschriftung der Anzeigebereiche.
![LaserCutter Display](./doc/Screenshot1.svg)
![Konfiguration](./doc/Screenshot2.svg)
![OTA Firmware-Update](./doc/Screenshot3.svg)
## 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`
```json
{
"session_s": 125,
"total_min": "42.50",
"gratiszeit_s": 20,
"ip": "192.168.1.100"
}
```
### JSON-Format `lasercutter/status`
```json
{
"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`:
```json
{"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
```bash
# 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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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 `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.
### Test 6.5 MqttClient (Phase 6)
> ⚠️ Voraussetzung: `test_sketches/mqtt_test_secrets.h` anlegen (Vorlage: `mqtt_test_secrets.h.example`)
```bash
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 halten**`resetTotal()` → 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)
```bash
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](https://www.conventionalcommits.org/)** 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.