Created
October 30, 2025 17:50
-
-
Save bigjosh/d2186b7ed3242e472312ae262f89fc44 to your computer and use it in GitHub Desktop.
AA LED animation for Room Service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <FastLED.h> | |
| #define LED_TYPE WS2812B | |
| #define COLOR_ORDER GRB | |
| // Configuration for 3 LED strings | |
| const int NUM_STRINGS = 3; | |
| // String 1 - Pin D4 | |
| #define LED_PIN_1 4 | |
| #define NUM_LEDS_1 120 | |
| CRGB leds1[NUM_LEDS_1]; | |
| // String 2 - Pin D5 | |
| #define LED_PIN_2 5 | |
| #define NUM_LEDS_2 120 | |
| CRGB leds2[NUM_LEDS_2]; | |
| // String 3 - Pin D6 | |
| #define LED_PIN_3 6 | |
| #define NUM_LEDS_3 120 | |
| CRGB leds3[NUM_LEDS_3]; | |
| // Parameters for each string | |
| struct StringParams { | |
| CRGB* leds; | |
| int numLeds; | |
| int numWaves; | |
| float* wavePositions; | |
| float waveWidth; | |
| float waveSpacing; | |
| float baseSpeed; | |
| float speedModPeriod; | |
| float speedVariation; | |
| uint8_t baseBrightness; | |
| float brightnessModPeriod; | |
| float brightnessVariation; | |
| uint8_t hue; | |
| uint8_t saturation; | |
| }; | |
| // Wave position arrays for each string | |
| float wavePositions1[4]; | |
| float wavePositions2[4]; | |
| float wavePositions3[4]; | |
| // String 1 Parameters (Original - organic motion) | |
| StringParams string1 = { | |
| leds1, NUM_LEDS_1, | |
| 4, wavePositions1, | |
| 12.0, 30.0, // wave width, spacing | |
| 20.0, 5.0, 0.8, // base speed, speed mod period, speed variation (±80%, can go backward) | |
| 180, 3.0, 0.3, // base brightness, brightness mod period, brightness variation | |
| 0, 255 // hue (red), saturation | |
| }; | |
| // String 2 Parameters (Faster, more erratic) | |
| StringParams string2 = { | |
| leds2, NUM_LEDS_2, | |
| 4, wavePositions2, | |
| 10.0, 28.0, // wave width, spacing | |
| 13.0, 3.5, 0.9, // base speed, speed mod period, speed variation (±90%, more erratic) | |
| 200, 2.5, 0.4, // base brightness, brightness mod period, brightness variation | |
| 0, 255 // hue (red), saturation | |
| }; | |
| // String 3 Parameters (Slower, more organic) | |
| StringParams string3 = { | |
| leds3, NUM_LEDS_3, | |
| 4, wavePositions3, | |
| 15.0, 35.0, // wave width, spacing | |
| 15.0, 6.5, 0.85, // base speed, speed mod period, speed variation (±85%) | |
| 160, 3.5, 0.25, // base brightness, brightness mod period, brightness variation | |
| 0, 255 // hue (red), saturation | |
| }; | |
| StringParams strings[NUM_STRINGS] = {string1, string2, string3}; | |
| // Scene management | |
| enum Scene { | |
| SCENE_DOTS, | |
| SCENE_FLASH, | |
| SCENE_THEATER_CHASE, | |
| SCENE_BLOBS, | |
| SCENE_FINALE | |
| }; | |
| const uint32_t SCENE_DURATIONS[] = { | |
| 30000, // dots - 30s | |
| 15000, // flash - 15s | |
| 30000, // theater chase - 30s | |
| 60000, // blobs - 60s | |
| 5000 // finale - 5s | |
| }; | |
| Scene currentScene = SCENE_DOTS; | |
| unsigned long sceneStartTime = 0; | |
| unsigned long lastUpdate = 0; | |
| float timeAccumulator = 0.0; | |
| // Global color (red, but can be changed) | |
| const uint8_t GLOBAL_HUE = 0; // Red | |
| const uint8_t GLOBAL_SAT = 255; | |
| // Scene-specific variables | |
| unsigned long lastFlashTime = 0; | |
| int theaterChasePosition = 0; | |
| unsigned long finaleStartTime = 0; | |
| bool finaleFlashDone = false; | |
| void setup() { | |
| // Initialize FastLED for all 3 strings | |
| FastLED.addLeds<LED_TYPE, LED_PIN_1, COLOR_ORDER>(leds1, NUM_LEDS_1); | |
| FastLED.addLeds<LED_TYPE, LED_PIN_2, COLOR_ORDER>(leds2, NUM_LEDS_2); | |
| FastLED.addLeds<LED_TYPE, LED_PIN_3, COLOR_ORDER>(leds3, NUM_LEDS_3); | |
| FastLED.setCorrection(TypicalLEDStrip); // Apply color correction | |
| //FastLED.setMaxRefreshRate(400); // Set explicit refresh rate for multiple outputs | |
| // Initialize wave positions for each string | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| StringParams& str = strings[s]; | |
| for (int i = 0; i < str.numWaves; i++) { | |
| str.wavePositions[i] = i * str.waveSpacing - str.waveWidth; | |
| } | |
| } | |
| lastUpdate = millis(); | |
| sceneStartTime = millis(); | |
| randomSeed(analogRead(0)); // Seed random for dots and sparkles | |
| } | |
| // Scene: Flickering dots randomly along strings | |
| void sceneDots() { | |
| // Clear all strings | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| fill_solid(strings[s].leds, strings[s].numLeds, CRGB::Black); | |
| } | |
| // Randomly light dots on each string | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| StringParams& str = strings[s]; | |
| for (int i = 0; i < str.numLeds; i++) { | |
| // ~5% chance per LED to flicker | |
| if (random(100) < 5) { | |
| uint8_t brightness = random(50, 255); | |
| str.leds[i] = CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness); | |
| } | |
| } | |
| } | |
| } | |
| // Scene: Full string flashes | |
| void sceneFlash() { | |
| unsigned long currentTime = millis(); | |
| // Random delay between flashes (~200ms average = ~5 flashes/sec) | |
| if (currentTime - lastFlashTime > random(100, 300)) { | |
| // Flash all strings | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| fill_solid(strings[s].leds, strings[s].numLeds, CHSV(GLOBAL_HUE, GLOBAL_SAT, 255)); | |
| } | |
| lastFlashTime = currentTime; | |
| } else { | |
| // Turn off | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| fill_solid(strings[s].leds, strings[s].numLeds, CRGB::Black); | |
| } | |
| } | |
| } | |
| // Scene: Theater chase | |
| void sceneTheaterChase() { | |
| static unsigned long lastChaseUpdate = 0; | |
| unsigned long currentTime = millis(); | |
| // Update every 100ms | |
| if (currentTime - lastChaseUpdate > 100) { | |
| theaterChasePosition++; | |
| if (theaterChasePosition >= 3) theaterChasePosition = 0; | |
| lastChaseUpdate = currentTime; | |
| } | |
| // Clear and draw theater chase | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| StringParams& str = strings[s]; | |
| fill_solid(str.leds, str.numLeds, CRGB::Black); | |
| for (int i = theaterChasePosition; i < str.numLeds; i += 3) { | |
| str.leds[i] = CHSV(GLOBAL_HUE, GLOBAL_SAT, 200); | |
| } | |
| } | |
| } | |
| // Scene: Blobs (original peristaltic animation) | |
| void sceneBlobs(float deltaTime) { | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| StringParams& str = strings[s]; | |
| // Calculate speed modulation for this string | |
| float speedMultiplier = 1.0 + str.speedVariation * sin(TWO_PI * timeAccumulator / str.speedModPeriod); | |
| float currentSpeed = str.baseSpeed * speedMultiplier; | |
| // Calculate brightness modulation | |
| float brightnessMultiplier = 1.0 + str.brightnessVariation * sin(TWO_PI * timeAccumulator / str.brightnessModPeriod); | |
| uint8_t globalBrightness = constrain(str.baseBrightness * brightnessMultiplier, 0, 255); | |
| // Calculate wave width modulation | |
| float widthModPeriod = str.speedModPeriod * 1.3; | |
| float widthMultiplier = 1.0 + 0.4 * sin(TWO_PI * timeAccumulator / widthModPeriod); | |
| float currentWaveWidth = str.waveWidth * widthMultiplier; | |
| // Clear the strip | |
| fill_solid(str.leds, str.numLeds, CRGB::Black); | |
| // Pre-calculate constants | |
| const float invWaveWidth = 1.0 / currentWaveWidth; | |
| const int rangeLimit = (int)(currentWaveWidth * 2.0 + 0.5); | |
| // Update and render each wave | |
| for (int w = 0; w < str.numWaves; w++) { | |
| str.wavePositions[w] += currentSpeed * deltaTime; | |
| if (str.wavePositions[w] > str.numLeds + currentWaveWidth * 2) { | |
| str.wavePositions[w] = -currentWaveWidth * 2; | |
| } | |
| int waveCenter = (int)str.wavePositions[w]; | |
| int startLED = max(0, waveCenter - rangeLimit); | |
| int endLED = min(str.numLeds, waveCenter + rangeLimit + 1); | |
| for (int i = startLED; i < endLED; i++) { | |
| float distance = abs(i - str.wavePositions[w]); | |
| float normalizedDist = distance * invWaveWidth; | |
| uint8_t distInt = (uint8_t)(normalizedDist * 64.0); | |
| uint16_t intensity; | |
| if (distInt > 128) { | |
| intensity = 0; | |
| } else { | |
| uint8_t oneMinusDist = 128 - distInt; | |
| uint16_t temp = oneMinusDist * 2; | |
| intensity = ((uint32_t)temp * temp * temp) >> 16; | |
| } | |
| uint8_t brightness = (intensity * globalBrightness) >> 8; | |
| if (brightness > 3) { | |
| CRGB waveColor = CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness); | |
| str.leds[i] = blend(str.leds[i], waveColor, brightness); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Scene: Finale - big flash decay + white sparkles | |
| void sceneFinale() { | |
| unsigned long currentTime = millis(); | |
| unsigned long elapsed = currentTime - finaleStartTime; | |
| if (!finaleFlashDone) { | |
| // Flash decay over 200ms | |
| if (elapsed < 200) { | |
| // Exponential decay | |
| uint8_t brightness = 255 * (1.0 - (elapsed / 200.0)); | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| fill_solid(strings[s].leds, strings[s].numLeds, CHSV(GLOBAL_HUE, GLOBAL_SAT, brightness)); | |
| } | |
| } else { | |
| finaleFlashDone = true; | |
| } | |
| } else { | |
| // Random white sparkles | |
| for (int s = 0; s < NUM_STRINGS; s++) { | |
| StringParams& str = strings[s]; | |
| fill_solid(str.leds, str.numLeds, CRGB::Black); | |
| // ~3% of LEDs sparkle white per frame | |
| for (int i = 0; i < str.numLeds; i++) { | |
| if (random(100) < 3) { | |
| str.leds[i] = CRGB::White; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void loop() { | |
| // Calculate delta time | |
| unsigned long currentTime = millis(); | |
| float deltaTime = (currentTime - lastUpdate) / 1000.0; | |
| lastUpdate = currentTime; | |
| timeAccumulator += deltaTime; | |
| // Check if scene should transition | |
| if (currentTime - sceneStartTime >= SCENE_DURATIONS[currentScene]) { | |
| // Move to next scene | |
| currentScene = (Scene)((currentScene + 1) % 5); | |
| sceneStartTime = currentTime; | |
| // Reset scene-specific variables | |
| if (currentScene == SCENE_FINALE) { | |
| finaleStartTime = currentTime; | |
| finaleFlashDone = false; | |
| } | |
| if (currentScene == SCENE_THEATER_CHASE) { | |
| theaterChasePosition = 0; | |
| } | |
| } | |
| // Render current scene | |
| switch (currentScene) { | |
| case SCENE_DOTS: | |
| sceneDots(); | |
| break; | |
| case SCENE_FLASH: | |
| sceneFlash(); | |
| break; | |
| case SCENE_THEATER_CHASE: | |
| sceneTheaterChase(); | |
| break; | |
| case SCENE_BLOBS: | |
| sceneBlobs(deltaTime); | |
| break; | |
| case SCENE_FINALE: | |
| sceneFinale(); | |
| break; | |
| } | |
| // Display with smooth updates | |
| FastLED.show(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment