#include "Display_ST7789.h" #include "SD_Card.h" #include "Display_PNG.h" #include "Audio_Player.h" #include "Web_Server.h" #include #define BOOT_KEY_PIN 0 // Playback states enum AppState { STATE_IDLE, STATE_PLAYING, STATE_AUTOPLAY, STATE_ERROR }; AppState currentState = STATE_IDLE; bool hasError = false; uint16_t currentIndex = 0; bool lastButtonState = HIGH; uint32_t lastDebounceTime = 0; const uint32_t debounceDelay = 50; // Slide Show timing uint32_t lastSlideTime = 0; const uint32_t slideShowInterval = 5000; // 5 seconds // Pulse control for RGB LED uint32_t lastLedPulseTime = 0; uint8_t pulseBrightness = 0; bool pulseDirection = true; // Click gesture timers uint32_t lastButtonReleaseTime = 0; uint32_t buttonPressStartTime = 0; uint8_t clickCount = 0; bool isButtonPressed = false; bool longPressTriggered = false; void setup() { Serial.begin(115200); delay(1000); printf("\r\n==========================================\r\n"); printf("Starting Sound and Picture Player...\r\n"); printf("==========================================\r\n"); // Initialize Display and Backlight first so the screen lights up immediately LCD_Init(); Set_Backlight(90); // Initialize other peripherals Flash_test(); SD_Init(); // Setup button pinMode(BOOT_KEY_PIN, INPUT_PULLUP); // Retrieve media files from SD Card Search_Images("/"); Search_Audio("/"); // Initialize ESP32-audioI2S player initAudioPlayer(); printf("Initialization complete!\r\n"); printf("Found %d PNG images and %d Audio files (.mp3/.wav).\r\n", Image_Count, Audio_Count); if (Image_Count == 0 || Audio_Count == 0) { hasError = true; currentState = STATE_ERROR; printf("ERROR: No media found on microSD card!\r\n"); } else { printf("Displaying initial image (index 0): %s\r\n", Image_Names[0]); Display_Image_By_Index("/", 0); currentState = STATE_IDLE; } // Start the SoftAP and local Web Server initWebServer(); } void playCurrentPair() { if (Image_Count == 0 && Audio_Count == 0) { printf("Cannot play: missing PNG images and Audio files!\r\n"); return; } // Stop current playing audio if any stopAudio(); printf("Playing item index %d:\r\n", currentIndex); // Display the image if (Image_Count > 0) { uint16_t pngIndex = currentIndex % Image_Count; Display_Image_By_Index("/", pngIndex); } // Play the audio bite if (Audio_Count > 0) { uint16_t audioIndex = currentIndex % Audio_Count; String soundPath = String("/") + Audio_Names[audioIndex]; printf("Playing audio: %s\r\n", soundPath.c_str()); playAudio(soundPath.c_str()); } } void handleSinglePress() { printf("Gesture: Single Click - Next Slide\r\n"); if (!hasError) { if (currentState == STATE_AUTOPLAY) { currentState = STATE_IDLE; printf("Autoplay Disabled.\r\n"); } currentIndex = (currentIndex + 1) % std::max(Image_Count, Audio_Count); playCurrentPair(); } } void handleDoublePress() { printf("Gesture: Double Click - Pause / Resume Audio\r\n"); if (!hasError) { pauseResumeAudio(); } } void handleTriplePress() { printf("Gesture: Triple Click - Previous Slide\r\n"); if (!hasError) { if (currentState == STATE_AUTOPLAY) { currentState = STATE_IDLE; printf("Autoplay Disabled.\r\n"); } uint16_t totalMedia = std::max(Image_Count, Audio_Count); currentIndex = (currentIndex + totalMedia - 1) % totalMedia; playCurrentPair(); } } void handleQuadruplePress() { if (!hasError) { uint8_t nextVol = getPlayerVolume() + 4; if (nextVol > 20) nextVol = 4; // Loop back setPlayerVolume(nextVol); } } void handleLongPress() { printf("Gesture: Long Press - Toggle Autoplay Slideshow\r\n"); if (!hasError) { if (currentState == STATE_AUTOPLAY) { currentState = STATE_IDLE; printf("Autoplay Disabled. Manual Mode active.\r\n"); stopAudio(); } else { currentState = STATE_AUTOPLAY; printf("Autoplay Enabled! Cycling slides every %d ms.\r\n", slideShowInterval); lastSlideTime = millis(); playCurrentPair(); } } } void updateLEDStatus() { // Disabled NeoPixel code to resolve hardware conflict with SPI SCLK (GPIO 38) } void loop() { bool currentButtonReading = digitalRead(BOOT_KEY_PIN); uint32_t now = millis(); // Detect state transitions for debouncing and gesture capturing if (currentButtonReading == LOW && lastButtonState == HIGH) { // Button Pressed down lastDebounceTime = now; buttonPressStartTime = now; isButtonPressed = true; longPressTriggered = false; } else if (currentButtonReading == LOW && lastButtonState == LOW && isButtonPressed) { // Button is held down if (!longPressTriggered && (now - buttonPressStartTime > 1500)) { longPressTriggered = true; handleLongPress(); } } else if (currentButtonReading == HIGH && lastButtonState == LOW) { // Button Released isButtonPressed = false; uint32_t pressDuration = now - buttonPressStartTime; if (!longPressTriggered && pressDuration > 50) { // Debounced release clickCount++; lastButtonReleaseTime = now; } } // Process click gestures after 300ms timeout has elapsed since last release if (clickCount > 0 && !isButtonPressed && (now - lastButtonReleaseTime > 300)) { if (clickCount == 1) { handleSinglePress(); } else if (clickCount == 2) { handleDoublePress(); } else if (clickCount == 3) { handleTriplePress(); } else if (clickCount >= 4) { handleQuadruplePress(); } clickCount = 0; // Reset counter } // Handle slideshow auto-advance in Autoplay mode if (currentState == STATE_AUTOPLAY && !hasError) { // Only advance when the audio stops playing and slideShowInterval (5s) has passed if (!isAudioPlaying() && (now - lastSlideTime > slideShowInterval)) { lastSlideTime = now; currentIndex = (currentIndex + 1) % std::max(Image_Count, Audio_Count); playCurrentPair(); } } lastButtonState = currentButtonReading; // Handle incoming web server clients handleWebServer(); // Update status RGB LED updateLEDStatus(); delay(10); // Yield to other RTOS tasks }