// ============================================================================= // main.cpp - Orchestrierung aller Module // // Prioritaeten: // 1. LaserTracker + Display: starten SOFORT, laufen IMMER // 2. WiFi: non-blocking, unterbricht niemals LaserTracker // 3. MQTT + WebServer: Lazy-Init beim ersten WiFi-Connect // // Display-Takte (rate-limited): // Modul 0 (W): blinkt 500ms wenn WiFi getrennt // Module 1-3: Minutenanzeige, Update bei Wertwechsel oder max. alle 60 s // Modul 4 (M): nur bei Statuswechsel // Module 5-7: Countdown/Ring/Idle, Update alle 1 s // ============================================================================= #include #include #include // esp_reset_reason() #include "config.h" #include "settings.h" #include "wifi_connector.h" #include "display_manager.h" #include "laser_tracker.h" #include "mqtt_client.h" #include "web_server.h" // Lazy-Init Flags (MQTT + WebServer erst wenn WiFi verfuegbar) static bool mqttStarted = false; static bool webStarted = false; void setup() { Serial.begin(SERIAL_BAUD_RATE); // Reset-Grund auslesen – erscheint als erstes nach jedem Neustart im Log const char* resetReason = "UNBEKANNT"; switch (esp_reset_reason()) { case ESP_RST_POWERON: resetReason = "POWER_ON"; break; case ESP_RST_SW: resetReason = "SOFTWARE (esp_restart)"; break; case ESP_RST_PANIC: resetReason = "PANIC/EXCEPTION"; break; case ESP_RST_INT_WDT: resetReason = "WATCHDOG (intern)"; break; case ESP_RST_TASK_WDT: resetReason = "WATCHDOG (Task)"; break; case ESP_RST_WDT: resetReason = "WATCHDOG"; break; case ESP_RST_DEEPSLEEP: resetReason = "DEEP_SLEEP"; break; case ESP_RST_BROWNOUT: resetReason = "BROWNOUT (Spannung zu niedrig!)"; break; case ESP_RST_SDIO: resetReason = "SDIO"; break; default: break; } // --- 1. Einstellungen laden (NVS) --- settings.begin(); LOG_I("MAIN", "=== Neustart === Grund: %s", resetReason); LOG_I("MAIN", "LaserCutter Display gestartet"); LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN); settings.printToSerial(); // --- 2. Display initialisieren (sofort betriebsbereit) --- display.begin(); display.printToSerial(); display.showWifiError(true); // W an bis WiFi-Status bekannt display.showMqttError(false); display.showLaserTime(0.0f); display.showIdle(); // --- 3. LaserTracker starten (PRIORITAET 1 - muss sofort laufen) --- laserTracker.begin(); laserTracker.printToSerial(); // --- 4. WiFi non-blocking starten --- // begin() kehrt SOFORT zurueck (kein Blockieren mehr!) // MQTT + WebServer starten spaeter (lazy, in loop()) wifiConnector.begin(); // --- 5. Watchdog --- esp_task_wdt_init(WDT_TIMEOUT_S, true); esp_task_wdt_add(NULL); LOG_I("MAIN", "Setup abgeschlossen. Watchdog aktiv (%d s)", WDT_TIMEOUT_S); } void loop() { // Watchdog zuruecksetzen esp_task_wdt_reset(); // ========================================================================== // PRIORITAET 1: LaserTracker (immer zuerst, niemals ueberspringen) // ========================================================================== laserTracker.loop(); // ========================================================================== // PRIORITAET 2: WiFi (non-blocking, kein delay/while) // ========================================================================== wifiConnector.loop(); // ========================================================================== // Lazy-Init: MQTT + WebServer beim ersten WiFi-Connect // ========================================================================== if (wifiConnector.isConnected()) { if (!mqttStarted) { mqttClient.begin(); mqttClient.printToSerial(); mqttStarted = true; } if (!webStarted) { LOG_I("MAIN", "WebServer wird gestartet..."); webServer.begin(); webStarted = true; } } // ========================================================================== // MQTT (nur wenn gestartet) // ========================================================================== if (mqttStarted) { mqttClient.loop(); // Session-Publish (nur wenn Netto-Zeit vorhanden, kein GRATIS-only) if (laserTracker.consumeSessionEnd()) { int lastSession = laserTracker.getLastSessionSeconds(); if (lastSession > 0) { mqttClient.publishSession(lastSession, settings.get().gratisSeconds); } } // Session-Reset-Publish: akkumulierte Sekunden vor dem Reset if (laserTracker.consumeSessionReset()) { int sec = laserTracker.getLastSessionSeconds(); if (sec > 0) { mqttClient.publishSession(sec, settings.get().gratisSeconds); } } } // WebServer-Loop: ArduinoOTA verarbeiten if (webStarted) { webServer.loop(); } // ========================================================================== // Display: rate-limited (Modul-Updates nur bei Bedarf) // ========================================================================== // --- Modul 0: WiFi-Fehler (blinkt 500ms wenn getrennt) --- { static uint32_t wifiBlinkMs = 0; static bool wifiBlinkOn = false; bool wifiErr = !wifiConnector.isConnected(); if (wifiErr) { if (millis() - wifiBlinkMs >= 500) { wifiBlinkMs = millis(); wifiBlinkOn = !wifiBlinkOn; display.showWifiError(wifiBlinkOn); } } else { if (wifiBlinkOn) { wifiBlinkOn = false; display.showWifiError(false); } } } // --- Module 1-3: Minutenanzeige (Update bei Wertwechsel oder alle 60 s) --- { static uint32_t lastMinUpdate = 0; static int lastMinValue = -1; int curMin = laserTracker.getAllSessionsSumMinutes(); if (curMin != lastMinValue || (millis() - lastMinUpdate) >= 60000UL) { display.showLaserTime((float)curMin); lastMinValue = curMin; lastMinUpdate = millis(); } } // --- Modul 4: MQTT-Fehler (nur bei Statuswechsel) --- { static bool lastMqttErr = false; bool mqttErr = mqttStarted ? !mqttClient.isConnected() : false; if (mqttErr != lastMqttErr) { display.showMqttError(mqttErr); lastMqttErr = mqttErr; } } // --- Module 5-7: Countdown/Ring/Idle (Update alle 1 s) --- { static uint32_t lastSecUpdate = 0; if (millis() - lastSecUpdate >= 1000UL) { lastSecUpdate = millis(); int countdown = laserTracker.getCountdownRemaining(); if (countdown > 0) { display.showCountdown(countdown); } else if (laserTracker.isActive()) { display.showSessionRing(laserTracker.getRunningSessionSeconds() % 60); } else { display.showIdle(); } } } display.update(); // Heap-Monitor: alle 30 s loggen static uint32_t lastHeapLog = 0; if (millis() - lastHeapLog >= 30000) { lastHeapLog = millis(); LOG_D("MAIN", "FreeHeap: %u Bytes", ESP.getFreeHeap()); } delay(20); // 50Hz loop, kurz genug fuer fluessiges Blinken }