diff --git a/README.md b/README.md index ccb7ee2..f4aa86a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Dieses Projekt implementiert einen ESP32-basierten MQTT-Client mit Dot-Matrix-Di 11. [Fehlerverhalten](#fehlerverhalten) 12. [Bibliotheken](#bibliotheken) 13. [Build & Flash](#build--flash) -14. [Beitragen / Commits](#beitragen--commits) +14. [Tests](#tests) +15. [Beitragen / Commits](#beitragen--commits) --- @@ -101,17 +102,19 @@ Es ist in **zwei Zonen** aufgeteilt, die unabhängig voneinander beschrieben wer > **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. -### Zonen-Belegung +### Modul-Belegung -| Zone | 0-Index-Bereich | Physisch | Anzeige-Inhalt | -|------|-----------------|----------------|---------------------------------------------------------| -| 0 | Modul 0–3 | Obere Reihe | Akkumulierte aktive Laserzeit in **Minuten** (z.B. `42.5`) | -| 1 | Modul 4–7 | Untere Reihe | **Countdown** Gratiszeit in Sekunden, danach `---` | +| 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) | -- Die Anzeige aktualisiert sich sekündlich, solange der Laser aktiv ist. -- Bei inaktivem Laser bleibt die letzte gemessene Zeit dauerhaft sichtbar. -- Fehlerzustände (WLAN, MQTT) werden in Zone 1 (untere Reihe) angezeigt. -- Bibliotheken: `MD_Parola` + `MD_MAX72XX` +- 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 `gratisSeconds` aktiv bleiben). +- Die Anzeige aktualisiert sich im `loop()` ohne Blocking-Delays. +- Bibliotheken: `MD_MAX72XX` (direkte Puffer-Steuerung, keine MD_Parola) --- @@ -128,11 +131,45 @@ Der potentialfreie Ausgang des Laser Cutters (z.B. Schließer-Kontakt über Opto ## Zeit-Tracking & Gratiszeit -- Die Laserzeit wird **akkumulativ** in Minuten gezählt und über Neustarts hinaus im **NVS (Non-Volatile Storage)** des ESP32 gespeichert. -- **Gratiszeit**: Eine konfigurierbare Zeitspanne (0–120 Sekunden) am Anfang jeder Session wird **nicht** zur akkumulierten Zeit gezählt. Dies erlaubt kurze Testläufe zum Einstellen des Laser Cutters ohne Kosten. - - Standard: 20 Sekunden - - Einstellbar über das Webinterface -- Die untere Display-Zeile zeigt während der Gratiszeit einen Countdown an. +### 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. --- @@ -222,35 +259,35 @@ Firmware-Updates können kabellos über die Weboberfläche unter `/update` einge ## Fehlerverhalten -| Zustand | Display-Anzeige (untere Zeile, Modul 1) | Verhalten | -|----------------------|----------------------------------------|----------------------------------------| -| WLAN getrennt | `WiFi ERR` | Zeiterfassung läuft weiter, Reconnect | -| MQTT nicht erreichbar| `MQTT ERR` | Zeiterfassung läuft weiter, Reconnect | -| WLAN + MQTT OK | Normaler Betrieb (Countdown/`---`) | – | +| 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_Parola` | Dot-Matrix-Display Textausgabe | -| `MD_MAX72XX` | Treiber für MAX7219/GYMAX7219 | -| `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 | +| 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 -# PlatformIO CLI -pio run --target upload +# Haupt-Firmware bauen und flashen +pio run -e az-delivery-devkit-v4 --target upload # Serieller Monitor pio device monitor @@ -265,21 +302,119 @@ Ziel-Board: `az-delivery-devkit-v4` (ESP32), Upload-Port: `COM3` ``` MQTT-Display-LaserCutter/ ├── src/ -│ └── main.cpp # Hauptprogramm +│ ├── 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-Logik (MD_Parola) -│ ├── laser_tracker.h # Signal-Detektion & Zeiterfassung -│ ├── mqtt_client.h # MQTT-Wrapper (PubSubClient) -│ ├── web_server.h # Webinterface (ESPAsyncWebServer) -│ └── settings.h # NVS-Persistenz (Preferences) -├── parser/ -│ └── shelly_parser.py # Separater Python-Parser für Shelly PM G3 +│ ├── 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 + +```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 1–3 (ganze Minuten: 0, 1, 42, 999) +- `showMqttError(true/false)` → Modul 4 +- `showCountdown(n)` → Module 5–7 +- `showIdle()` → Module 5–7 (`--`) + +### 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 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](https://www.conventionalcommits.org/)** für alle Git-Commit-Nachrichten. diff --git a/test_sketches/test_laser_tracker.cpp b/test_sketches/test_laser_tracker.cpp index 6de337d..be0a725 100644 --- a/test_sketches/test_laser_tracker.cpp +++ b/test_sketches/test_laser_tracker.cpp @@ -43,6 +43,7 @@ void setup() { Serial.println("========================================"); settings.begin(); + settings.saveGratisSeconds(DEFAULT_GRATIS_SECONDS); // NVS-Korrektur: setzt 20s settings.printToSerial(); display.begin(); @@ -98,7 +99,8 @@ void loop() { bootPressedAt = millis(); Serial.println("[BTN] BOOT gedrueckt - 3s halten fuer Reset..."); } else if ((millis() - bootPressedAt) >= RESET_HOLD_MS) { - Serial.println("[RESET] Gesamtzeit wird zurueckgesetzt!"); + Serial.println("[RESET] Alle Einstellungen + Gesamtzeit werden zurueckgesetzt!"); + settings.reset(); // NVS komplett auf Defaults (inkl. gratisSeconds=20) laserTracker.resetTotal(); display.showLaserTime(0.0f); display.showIdle();