From b7f28b27c9f1e874385918fd6207fbf2cf16af5e Mon Sep 17 00:00:00 2001 From: drjones Date: Tue, 30 Jun 2026 01:09:51 -0700 Subject: [PATCH] Implement UX improvements: status LED, long press slideshow toggle --- sound_picture_app/sound_picture_app.ino | 134 +++++++++++++++++++++--- 1 file changed, 120 insertions(+), 14 deletions(-) diff --git a/sound_picture_app/sound_picture_app.ino b/sound_picture_app/sound_picture_app.ino index 9ce46af..a4c9b59 100644 --- a/sound_picture_app/sound_picture_app.ino +++ b/sound_picture_app/sound_picture_app.ino @@ -5,12 +5,32 @@ #include #define BOOT_KEY_PIN 9 +#define PIN_NEOPIXEL 38 + +// 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; -bool buttonWasPressed = false; + +// 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; void setup() { Serial.begin(115200); @@ -20,6 +40,10 @@ void setup() { printf("Starting Sound and Picture Player...\r\n"); printf("==========================================\r\n"); + // Setup NeoPixel + pinMode(PIN_NEOPIXEL, OUTPUT); + neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); // turn off initially + // Initialize peripherals Flash_test(); SD_Init(); @@ -36,11 +60,14 @@ void setup() { printf("Initialization complete!\r\n"); printf("Found %d PNG images and %d WAV soundbites.\r\n", Image_Count, Wav_Count); - if (Image_Count > 0) { + if (Image_Count == 0 || Wav_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); - } else { - printf("WARNING: No PNG images found in SD Card root directory!\r\n"); + currentState = STATE_IDLE; } } @@ -70,31 +97,110 @@ void playCurrentPair() { } } +void updateLEDStatus() { + uint32_t now = millis(); + + // Pulse animation frequency + if (now - lastLedPulseTime > 15) { + lastLedPulseTime = now; + if (pulseDirection) { + pulseBrightness += 2; + if (pulseBrightness >= 120) pulseDirection = false; + } else { + pulseBrightness -= 2; + if (pulseBrightness <= 10) pulseDirection = true; + } + } + + // Update LED color based on state + if (hasError) { + // Flashing red + if ((now / 250) % 2 == 0) { + neopixelWrite(PIN_NEOPIXEL, 64, 0, 0); // Dim Red + } else { + neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + } + } + else if (isWavPlaying()) { + // Pulsing Green + neopixelWrite(PIN_NEOPIXEL, 0, pulseBrightness, 0); + } + else if (currentState == STATE_AUTOPLAY) { + // Pulsing Purple (Red + Blue) + neopixelWrite(PIN_NEOPIXEL, pulseBrightness / 2, 0, pulseBrightness); + } + else { + // Solid Blue + neopixelWrite(PIN_NEOPIXEL, 0, 0, 32); + } +} + void loop() { bool currentButtonReading = digitalRead(BOOT_KEY_PIN); + static uint32_t buttonPressStartTime = 0; + static bool buttonWasHeld = false; + // Detect changes in button state if (currentButtonReading != lastButtonState) { lastDebounceTime = millis(); + if (currentButtonReading == LOW) { + buttonPressStartTime = millis(); + buttonWasHeld = false; + } } if ((millis() - lastDebounceTime) > debounceDelay) { if (currentButtonReading == LOW) { - if (!buttonWasPressed) { - buttonWasPressed = true; - printf("Button pressed! Cycling to next picture and playing sound...\r\n"); - - if (Image_Count > 0 || Wav_Count > 0) { - playCurrentPair(); - currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count); - } else { - printf("No media found on SD card to cycle!\r\n"); + // Button is being held down. Check if it exceeds long-press threshold (1.5 seconds) + if (!buttonWasHeld && (millis() - buttonPressStartTime > 1500)) { + buttonWasHeld = true; // Mark as held to prevent repeated triggers + if (!hasError) { + if (currentState == STATE_AUTOPLAY) { + currentState = STATE_IDLE; + printf("Autoplay Disabled. Manual Mode active.\r\n"); + stopWav(); + } else { + currentState = STATE_AUTOPLAY; + printf("Autoplay Enabled! Cycling slides every %d ms.\r\n", slideShowInterval); + lastSlideTime = millis(); + playCurrentPair(); + } } } } else { - buttonWasPressed = false; + // Button was released + if (lastButtonState == LOW && !buttonWasHeld) { + // Single press trigger + printf("Single press detected!\r\n"); + if (!hasError) { + if (currentState == STATE_AUTOPLAY) { + currentState = STATE_IDLE; + printf("Autoplay Disabled via manual navigation.\r\n"); + } + playCurrentPair(); + currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count); + } else { + printf("System in error state (missing files). Can't cycle.\r\n"); + } + } + buttonWasHeld = false; + } + } + + // Handle slideshow auto-advance in Autoplay mode + if (currentState == STATE_AUTOPLAY && !hasError) { + // Advance if the audio finished playing and 5 seconds has elapsed + if (!isWavPlaying() && (millis() - lastSlideTime > slideShowInterval)) { + lastSlideTime = millis(); + currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count); + playCurrentPair(); } } lastButtonState = currentButtonReading; + + // Update status RGB LED + updateLEDStatus(); + delay(10); // Yield to other RTOS tasks }