From 179d586cf8df60906e11cb7f9f3edfdf314733b5 Mon Sep 17 00:00:00 2001 From: wwartana Date: Mon, 22 Dec 2025 12:22:49 +0800 Subject: [PATCH] Mode 7 Hari --- src/main.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3f32a53..886c4ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,6 +76,15 @@ bool ledState = false; unsigned long lastBuzzerBeep = 0; bool buzzerState = false; +// MP3 playback error detection (via DFPlayer serial messages) +bool playbackError = false; +unsigned long playbackErrorEndTime = 0; + +// DFPlayer auto-recovery +bool dfPlayerNeedsRecovery = false; +unsigned long lastRecoveryAttempt = 0; +const unsigned long RECOVERY_INTERVAL_MS = 5000; // Try recovery every 5 seconds + // Admin reset blinking effect bool adminResetBlinking = false; unsigned long adminResetEndTime = 0; @@ -230,12 +239,117 @@ void saveSchedules() { ////////////////////////////////////////////////////////////////////////// // DFPlayer ////////////////////////////////////////////////////////////////////////// + +// Parse DFPlayer messages and return error description +String getDFPlayerError(uint8_t type, int value) { + switch (type) { + case DFPlayerError: + switch (value) { + case Busy: return "Card not found"; + case Sleeping: return "Sleeping"; + case SerialWrongStack: return "Wrong Stack"; + case CheckSumNotMatch: return "Checksum Error"; + case FileIndexOut: return "File Index Out"; + case FileMismatch: return "File Mismatch"; + case Advertise: return "In Advertise"; + default: return "Unknown Error"; + } + default: + return ""; + } +} + +// Flag to prevent CardInserted loop after recovery +bool recoveryJustDone = false; + +// Check DFPlayer for messages and handle errors +void checkDFPlayerMessage() { + static bool sdCardPresent = true; // Assume present at start + + if (myDFPlayer.available()) { + uint8_t type = myDFPlayer.readType(); + int value = myDFPlayer.read(); + + if (type == DFPlayerError) { + String errorMsg = getDFPlayerError(type, value); + Serial.printf("[MP3] DFPlayer Error: %s (code: %d)\n", errorMsg.c_str(), value); + + // Trigger error state for SD card related errors + if (value == Busy || value == FileIndexOut || value == FileMismatch) { + playbackError = true; + dfPlayerNeedsRecovery = true; + playbackErrorEndTime = millis() + 10000UL; + sdCardPresent = false; + + // Notify via WebSocket + DynamicJsonDocument d(256); + d["type"] = "log"; + d["msg"] = "⚠️ DFPlayer Error: " + errorMsg; + sendJsonWs(d); + } + } else if (type == DFPlayerCardInserted) { + Serial.println("[MP3] SD Card inserted"); + // Skip if recovery was just done (to prevent loop from begin()) + if (recoveryJustDone) { + recoveryJustDone = false; + Serial.println("[MP3] Ignoring CardInserted after recovery"); + } else if (!sdCardPresent || playbackError) { + // Only trigger recovery if card was previously removed or there's an error + dfPlayerNeedsRecovery = true; + DynamicJsonDocument d(256); + d["type"] = "log"; + d["msg"] = "💾 SD Card terdeteksi, mencoba recovery..."; + sendJsonWs(d); + } + sdCardPresent = true; + } else if (type == DFPlayerCardRemoved) { + Serial.println("[MP3] SD Card removed"); + sdCardPresent = false; + playbackError = true; + dfPlayerNeedsRecovery = true; + playbackErrorEndTime = millis() + 10000UL; + + DynamicJsonDocument d(256); + d["type"] = "log"; + d["msg"] = "⚠️ SD Card dilepas!"; + sendJsonWs(d); + } else if (type == DFPlayerPlayFinished) { + Serial.printf("[MP3] Track %d finished\n", value); + isPlaying = false; + } + } +} + void initDFPlayer() { dfSerial.begin(9600, SERIAL_8N1, DFPLAYER_RX_PIN, DFPLAYER_TX_PIN); vTaskDelay(pdMS_TO_TICKS(200)); myDFPlayer.setTimeOut(600); // Fix for clone MP3 players if (myDFPlayer.begin(dfSerial)) { myDFPlayer.volume(30); // 0..30 (Max volume) + dfPlayerNeedsRecovery = false; + Serial.println("[MP3] DFPlayer initialized successfully"); + } else { + dfPlayerNeedsRecovery = true; + Serial.println("[MP3] DFPlayer initialization failed"); + } +} + +void recoverDFPlayer() { + Serial.println("[MP3] Attempting DFPlayer recovery..."); + myDFPlayer.setTimeOut(600); + if (myDFPlayer.begin(dfSerial)) { + myDFPlayer.volume(30); + dfPlayerNeedsRecovery = false; + playbackError = false; + recoveryJustDone = true; // Set flag to ignore next CardInserted event + Serial.println("[MP3] DFPlayer recovery successful!"); + // Notify via WebSocket + DynamicJsonDocument d(256); + d["type"] = "log"; + d["msg"] = "✅ DFPlayer pulih! Sistem siap kembali."; + sendJsonWs(d); + } else { + Serial.println("[MP3] DFPlayer recovery failed"); } } @@ -250,8 +364,11 @@ void playTrack(uint16_t track, const char* desc) { relayHoldUntil = millis() + 5000UL; } - myDFPlayer.playMp3Folder(track); // as requested + myDFPlayer.playMp3Folder(track); isPlaying = true; + playbackError = false; // Reset error state + dfPlayerNeedsRecovery = false; // Reset recovery flag for new playback attempt + // notify DynamicJsonDocument doc(256); doc["type"] = "log"; @@ -797,6 +914,9 @@ void loop(){ testButton.tick(); resetButton.tick(); + // Check for DFPlayer messages (errors, card insert/remove, etc.) + checkDFPlayerMessage(); + // Process TEST_BUTTON click: toggle relay manually if (testBtnClickPending) { testBtnClickPending = false; @@ -920,8 +1040,18 @@ void loop(){ relayOn = false; } - // Buzzer warning if date < 2025 - if (now.year() < 2025) { + // DFPlayer auto-recovery: reset and set volume after error + if (dfPlayerNeedsRecovery && millis() - lastRecoveryAttempt >= RECOVERY_INTERVAL_MS) { + lastRecoveryAttempt = millis(); + recoverDFPlayer(); + // Recovery done - stop buzzer + playbackError = false; + playbackErrorEndTime = 0; + } + + // Buzzer warning if date < 2025 OR playback error + bool buzzerCondition = (now.year() < 2025) || (playbackError && millis() < playbackErrorEndTime); + if (buzzerCondition) { if (millis() - lastBuzzerBeep >= 2000) { lastBuzzerBeep = millis(); buzzerState = true;