// ============================================================================= // laser_tracker.cpp - Implementierung // ============================================================================= #include "laser_tracker.h" // Globale Instanz LaserTracker laserTracker; // --------------------------------------------------------------------------- LaserTracker::LaserTracker() : _debouncedActive(false) , _pendingActive(false) , _debounceRunning(false) , _debounceStartMs(0) , _state(SessionState::INACTIVE) , _sessionStartMs(0) , _sessionStartTime(0) , _netStartMs(0) , _sessionNetSec(0) , _sessionNetMin(0) , _totalMinutesBase(0.0f) , _lastSessionSec(0) , _sessionEndPending(false) {} // --------------------------------------------------------------------------- void LaserTracker::begin() { pinMode(LASER_SIGNAL_PIN, INPUT_PULLUP); // NVS-Gesamtzeit laden (via SettingsManager) _totalMinutesBase = settings.get().totalMinutes; resetSession(); Serial.printf("[LaserTracker] begin - Pin=%d totalMin=%.2f gratis=%ds pol=%s\n", LASER_SIGNAL_PIN, _totalMinutesBase, gratisSeconds(), (settings.get().signalPolarity == SIGNAL_POL_HIGH_ACTIVE) ? "HIGH" : "LOW"); } // --------------------------------------------------------------------------- void LaserTracker::loop() { bool raw = readRaw(); // ------- Debounce ------------------------------------------------------- if (raw != _pendingActive) { _pendingActive = raw; _debounceRunning = true; _debounceStartMs = millis(); } if (_debounceRunning && (millis() - _debounceStartMs) >= (uint32_t)LASER_DEBOUNCE_MS) { _debounceRunning = false; if (_pendingActive != _debouncedActive) { _debouncedActive = _pendingActive; if (_debouncedActive) { onSessionStart(); } else { onSessionEnd(); } } } // ------- Session-Zustandsmaschine (Timer-Uebergaenge) ----------------- if (_state == SessionState::GRATIS) { uint32_t elapsed = (millis() - _sessionStartMs) / 1000; if (elapsed >= (uint32_t)gratisSeconds()) { _state = SessionState::NET_COUNTING; _netStartMs = millis(); Serial.println("[LaserTracker] GRATIS abgelaufen -> NET_COUNTING"); } } } // --------------------------------------------------------------------------- // Ereignis: Laser AN (nach Debounce) void LaserTracker::onSessionStart() { _state = SessionState::GRATIS; _sessionStartMs = millis(); _sessionStartTime = time(nullptr); _netStartMs = 0; Serial.println("[LaserTracker] SessionStart -> GRATIS"); } time_t LaserTracker::getSessionStartTime() const { return _sessionStartTime; } // --------------------------------------------------------------------------- // Ereignis: Laser AUS (nach Debounce) void LaserTracker::onSessionEnd() { // Gesamte Session-Dauer (inkl. Gratiszeit) -> NVS int totalSessionSec = (int)((millis() - _sessionStartMs) / 1000); if (_state == SessionState::NET_COUNTING) { int netSec = (int)((millis() - _netStartMs) / 1000); _sessionNetSec += netSec; // Display: nur Netto (ohne Gratis) _sessionNetMin += (netSec + 59) / 60; // Ceiling pro Session _lastSessionSec = netSec; _totalMinutesBase += totalSessionSec / 60.0f; // NVS: Netto + Gratis // NVS via SettingsManager speichern settings.saveTotalMinutes(_totalMinutesBase); Serial.printf("[LaserTracker] SessionEnd: gesamt=%ds netto=%ds " "sessionNetSec=%d total=%.2fmin\n", totalSessionSec, netSec, _sessionNetSec, _totalMinutesBase); } else if (_state == SessionState::GRATIS) { // Laser ging aus bevor Gratiszeit ablief -> nur Gratiszeit in NVS, keine Session _lastSessionSec = 0; _totalMinutesBase += totalSessionSec / 60.0f; settings.saveTotalMinutes(_totalMinutesBase); Serial.printf("[LaserTracker] SessionEnd: nur Gratiszeit gesamt=%ds " "total=%.2fmin\n", totalSessionSec, _totalMinutesBase); } _state = SessionState::INACTIVE; _sessionEndPending = true; // Fuer consumeSessionEnd() in main } // --------------------------------------------------------------------------- // Laufende Netto-Sekunden der aktuellen Session (live, waehrend NET_COUNTING) int LaserTracker::currentSessionNetSec() const { if (_state != SessionState::NET_COUNTING) return 0; return (int)((millis() - _netStartMs) / 1000); } // --------------------------------------------------------------------------- int LaserTracker::getAllSessionsSumMinutes() const { // Pro Session aufgerundete Minuten + laufende Session (ebenfalls Ceiling) int liveSec = currentSessionNetSec(); int liveMin = (liveSec > 0) ? (liveSec + 59) / 60 : 0; return _sessionNetMin + liveMin; } // --------------------------------------------------------------------------- int LaserTracker::getCountdownRemaining() const { if (_state != SessionState::GRATIS) return 0; int elapsed = (int)((millis() - _sessionStartMs) / 1000); int rem = gratisSeconds() - elapsed; return (rem > 0) ? rem : 0; } // --------------------------------------------------------------------------- bool LaserTracker::isActive() const { return _debouncedActive; } // --------------------------------------------------------------------------- float LaserTracker::getTotalMinutes() const { return _totalMinutesBase + (_sessionNetSec + currentSessionNetSec()) / 60.0f; } // --------------------------------------------------------------------------- int LaserTracker::getLastSessionSeconds() const { return _lastSessionSec; } // --------------------------------------------------------------------------- int LaserTracker::getRunningSessionSeconds() const { return currentSessionNetSec(); } // --------------------------------------------------------------------------- void LaserTracker::resetSession() { _sessionNetSec = 0; _sessionNetMin = 0; Serial.println("[LaserTracker] Session zurueckgesetzt (NVS unveraendert)"); } // --------------------------------------------------------------------------- void LaserTracker::resetTotal() { _totalMinutesBase = 0.0f; settings.saveTotalMinutes(0.0f); resetSession(); Serial.println("[LaserTracker] Gesamtzeit und Session auf 0 gesetzt"); } // --------------------------------------------------------------------------- void LaserTracker::printToSerial() const { const char* stateStr = "INACTIVE"; if (_state == SessionState::GRATIS) stateStr = "GRATIS"; if (_state == SessionState::NET_COUNTING) stateStr = "NET_COUNTING"; Serial.printf("[LaserTracker] state=%-12s active=%d sessionMin=%d " "countdown=%ds totalMin=%.2f\n", stateStr, _debouncedActive, getAllSessionsSumMinutes(), getCountdownRemaining(), getTotalMinutes()); } // --------------------------------------------------------------------------- // Einmalig true wenn eine Session gerade beendet wurde (consume-Semantik) bool LaserTracker::consumeSessionEnd() { if (_sessionEndPending) { _sessionEndPending = false; return true; } return false; } // --------------------------------------------------------------------------- bool LaserTracker::readRaw() const { bool pinHigh = digitalRead(LASER_SIGNAL_PIN); // SIGNAL_POL_HIGH_ACTIVE: HIGH = Laser AN // SIGNAL_POL_LOW_ACTIVE: LOW = Laser AN (INPUT_PULLUP Standard) return (settings.get().signalPolarity == SIGNAL_POL_HIGH_ACTIVE) ? pinHigh : !pinHigh; } int LaserTracker::gratisSeconds() const { return (int)settings.get().gratisSeconds; }