#include <FastLED.h>
// Pin Configuration
#define RING1_PIN 6 // Inner ring (1 LED)
#define RING2_PIN 7 // Second ring (7 LEDs)
#define RING3_PIN 8 // Third ring (11 LEDs)
#define RING4_PIN 9 // Outer ring (16 LEDs)
#define MODE_PIN 2 // Optional: Connect to button for mode switching
// LED Ring Configuration
#define RING1_LEDS 1
#define RING2_LEDS 7
#define RING3_LEDS 11
#define RING4_LEDS 16
// Define separate arrays for each ring
CRGB ring1[RING1_LEDS];
CRGB ring2[RING2_LEDS];
CRGB ring3[RING3_LEDS];
CRGB ring4[RING4_LEDS];
// Mode configuration
enum SystemMode {
MODE_STANDARD = 0, // Standard sunrise/sunset
MODE_DRAMATIC = 1, // More vibrant colors
MODE_ASTRONOMY = 2, // Realistic astronomical timing
MODE_TROPICAL = 3, // Vibrant tropical sunrise/sunset
MODE_ARCTIC = 4, // Long sunset/sunrise, short day/night
MODE_FAST_DEMO = 5, // Quick demo cycle
MODE_COUNT
};
SystemMode currentMode = MODE_STANDARD;
unsigned long lastModeCheck = 0;
const unsigned long MODE_CHECK_INTERVAL = 100; // Check mode button every 100ms
/////// TIMING CONFIGURATION ///////
// Timing presets for different modes (all in milliseconds)
struct TimingPreset {
unsigned long fadeTime; // Time for full fade cycle
unsigned long holdTimeDark; // Time to hold in darkness
unsigned long daylightDuration; // Time to hold at full daylight
unsigned long moonlightDuration; // Time to hold at full moonlight
};
// Timing presets for each mode - adjust these as desired
TimingPreset timingPresets[MODE_COUNT] = {
// Standard mode
{ 10000, 300, 20000, 20000 },
// Dramatic mode - faster transitions
{ 3000, 200, 1000, 800 },
// Astronomy mode - realistic daylight-to-night ratio
{ 4500, 200, 3000, 2000 },
// Tropical mode - short transitions, long daylight
{ 2000, 300, 3000, 800 },
// Arctic mode - long transitions, short daylight
{ 10000, 500, 500, 1500 },
// Fast demo mode - ultra quick for demonstration
{ 1000, 100, 300, 200 }
};
// Current timing configuration based on selected mode
unsigned long FADE_TIME = 500000;
unsigned long HOLD_TIME_DARK = 300;
unsigned long DAYLIGHT_DURATION = 200000;
unsigned long MOONLIGHT_DURATION = 15000;
unsigned long RING_DELAY = 10;
// Animation percentages - these control the animation phases
const uint8_t DAWN_START = 0; // Dawn begins
const uint8_t DAWN_END = 15; // Dawn ends, sunrise begins
const uint8_t SUNRISE_END = 40; // Sunrise ends, full daylight begins
const uint8_t SUNSET_START = 60; // Sunset begins
const uint8_t SUNSET_PEAK = 85; // Deep sunset colors
const uint8_t SUNSET_END = 100; // Complete darkness
const uint8_t MOONRISE_START = 0; // Moonrise begins
const uint8_t MOONRISE_END = 20; // Full moonlight begins
const uint8_t MOONSET_START = 80; // Moonset begins
const uint8_t MOONSET_END = 100; // Complete darkness
/////// COLOR CONFIGURATION ///////
// Define color palettes for different modes
struct ColorPalette {
CRGB dawnColor;
CRGB sunriseColor;
CRGB daylight;
CRGB sunsetColor;
CRGB moonlight;
};
// Color presets for each mode
ColorPalette colorPresets[MODE_COUNT] = {
// Standard mode
{
CRGB(255, 30, 0), // Dawn: Deep orange
CRGB(255, 160, 60), // Sunrise: Bright orange-yellow
CRGB(255, 255, 220), // Daylight: Warm white
CRGB(255, 40, 0), // Sunset: Deep red-orange
CRGB(180, 220, 255) // Moonlight: Cool blue-white
},
// Dramatic mode
{
CRGB(255, 0, 0), // Dawn: Pure red
CRGB(255, 100, 0), // Sunrise: Vibrant orange
CRGB(255, 255, 255), // Daylight: Pure white
CRGB(255, 0, 80), // Sunset: Red-purple
CRGB(80, 120, 255) // Moonlight: Rich blue
},
// Astronomy mode
{
CRGB(200, 40, 10), // Dawn: Subtle red-orange
CRGB(230, 150, 80), // Sunrise: Soft gold
CRGB(245, 245, 235), // Daylight: Natural white
CRGB(240, 80, 20), // Sunset: Realistic orange sunset
CRGB(160, 200, 255) // Moonlight: Pale blue-white
},
// Tropical mode
{
CRGB(255, 40, 20), // Dawn: Vivid red
CRGB(255, 170, 0), // Sunrise: Golden yellow
CRGB(255, 240, 200), // Daylight: Warm tropical sun
CRGB(255, 50, 20), // Sunset: Deep orange-red
CRGB(100, 160, 255) // Moonlight: Tropical night blue
},
// Arctic mode
{
CRGB(160, 120, 170), // Dawn: Purple-blue
CRGB(200, 180, 255), // Sunrise: Lavender-blue
CRGB(230, 240, 255), // Daylight: Cool arctic white
CRGB(220, 130, 170), // Sunset: Pink-purple
CRGB(100, 140, 230) // Moonlight: Deep blue
},
// Fast demo mode
{
CRGB(255, 0, 0), // Dawn: Red
CRGB(255, 255, 0), // Sunrise: Yellow
CRGB(255, 255, 255), // Daylight: White
CRGB(255, 0, 255), // Sunset: Magenta
CRGB(0, 0, 255) // Moonlight: Blue
}
};
// Current color palette based on selected mode
CRGB DAWN_COLOR;
CRGB SUNRISE_COLOR;
CRGB DAYLIGHT;
CRGB SUNSET_COLOR;
CRGB MOONLIGHT;
// State tracking
enum RingStateType {
STATE_SUNRISE = 0,
STATE_DAYLIGHT = 1,
STATE_SUNSET = 2,
STATE_DARK1 = 3,
STATE_MOONRISE = 4,
STATE_MOONLIGHT = 5,
STATE_MOONSET = 6,
STATE_DARK2 = 7
};
const char* modeNames[] = {
"STANDARD", "DRAMATIC", "ASTRONOMY", "TROPICAL", "ARCTIC", "FAST_DEMO"
};
struct RingState {
float fadeProgress;
unsigned long startTime;
bool active;
RingStateType state;
};
RingState ringStates[4];
void setup() {
// Initialize serial communication
Serial.begin(115200);
// Initialize button pins if using hardware buttons
pinMode(MODE_PIN, INPUT_PULLUP);
// Initialize each ring separately
FastLED.addLeds<WS2812B, RING1_PIN, GRB>(ring1, RING1_LEDS);
FastLED.addLeds<WS2812B, RING2_PIN, GRB>(ring2, RING2_LEDS);
FastLED.addLeds<WS2812B, RING3_PIN, GRB>(ring3, RING3_LEDS);
FastLED.addLeds<WS2812B, RING4_PIN, GRB>(ring4, RING4_LEDS);
FastLED.setBrightness(255);
// Initialize ring states
for(int i = 0; i < 4; i++) {
ringStates[i].fadeProgress = 0;
ringStates[i].startTime = 0;
ringStates[i].active = false;
ringStates[i].state = STATE_SUNRISE;
}
// Apply initial mode settings
applyModeSettings();
// Start first ring
ringStates[0].active = true;
ringStates[0].startTime = millis();
// Display initial mode
Serial.print(F("Current mode: "));
Serial.println(modeNames[currentMode]);
}
// Apply settings based on current mode
void applyModeSettings() {
// Apply timing settings from preset
FADE_TIME = timingPresets[currentMode].fadeTime;
HOLD_TIME_DARK = timingPresets[currentMode].holdTimeDark;
DAYLIGHT_DURATION = timingPresets[currentMode].daylightDuration;
MOONLIGHT_DURATION = timingPresets[currentMode].moonlightDuration;
// Apply color settings from preset
DAWN_COLOR = colorPresets[currentMode].dawnColor;
SUNRISE_COLOR = colorPresets[currentMode].sunriseColor;
DAYLIGHT = colorPresets[currentMode].daylight;
SUNSET_COLOR = colorPresets[currentMode].sunsetColor;
MOONLIGHT = colorPresets[currentMode].moonlight;
// Mode-specific adjustments
if(currentMode == MODE_ASTRONOMY) {
// 12-hour day/night cycle with appropriate ratios
RING_DELAY = 20; // More delay between rings
} else if(currentMode == MODE_ARCTIC) {
// Long transitions
RING_DELAY = 30; // Longest delay between rings
} else {
RING_DELAY = 10; // Standard delay
}
}
void setRingColor(CRGB* ring, int numLeds, CRGB color, uint8_t brightness) {
for(int i = 0; i < numLeds; i++) {
ring[i] = color;
ring[i].nscale8(brightness); // Apply brightness
}
}
// Enhanced color transitions with special effects based on current mode
void setRingColorEnhanced(CRGB* ring, int numLeds, CRGB color, uint8_t brightness, int ringIndex) {
if(currentMode == MODE_DRAMATIC) {
// Dramatic mode: Add sparkling effect
for(int i = 0; i < numLeds; i++) {
ring[i] = color;
// Random sparkle effect during transitions
if((ringStates[ringIndex].state == STATE_SUNRISE ||
ringStates[ringIndex].state == STATE_SUNSET) &&
random8() < 20) {
ring[i] = CRGB::White;
}
// Apply brightness
ring[i].nscale8(brightness);
}
}
else if(currentMode == MODE_TROPICAL) {
// Tropical mode: Slight red and orange variations
for(int i = 0; i < numLeds; i++) {
ring[i] = color;
// Add color variations for tropical sunrise/sunset
if(ringStates[ringIndex].state == STATE_SUNRISE ||
ringStates[ringIndex].state == STATE_SUNSET) {
ring[i].r = qadd8(ring[i].r, random8(0, 30));
ring[i].g = qsub8(ring[i].g, random8(0, 20));
}
// Apply brightness
ring[i].nscale8(brightness);
}
}
else if(currentMode == MODE_ARCTIC) {
// Arctic mode: Add pulsing blue/purple aurora effect during night
for(int i = 0; i < numLeds; i++) {
ring[i] = color;
// Aurora effect during moonlight
if(ringStates[ringIndex].state == STATE_MOONLIGHT) {
uint8_t wave = sin8(millis() / 200 + i * 20);
ring[i].b = qadd8(ring[i].b, wave / 5);
ring[i].g = qadd8(ring[i].g, wave / 8);
}
// Apply brightness
ring[i].nscale8(brightness);
}
}
else {
// Standard color setting for other modes
for(int i = 0; i < numLeds; i++) {
ring[i] = color;
ring[i].nscale8(brightness);
}
}
}
void handleRing(int ringIndex, unsigned long currentMillis) {
RingState* state = &ringStates[ringIndex];
CRGB* ring;
int numLeds;
// Select ring parameters
switch(ringIndex) {
case 0: ring = ring1; numLeds = RING1_LEDS; break;
case 1: ring = ring2; numLeds = RING2_LEDS; break;
case 2: ring = ring3; numLeds = RING3_LEDS; break;
case 3: ring = ring4; numLeds = RING4_LEDS; break;
}
// Activate next ring after delay when this ring reaches dawn end
if(ringIndex < 3 && state->active && state->state == STATE_SUNRISE &&
state->fadeProgress >= DAWN_END && !ringStates[ringIndex + 1].active) {
ringStates[ringIndex + 1].active = true;
ringStates[ringIndex + 1].startTime = currentMillis;
}
if(!state->active) {
// Ring not active yet, keep it dark
setRingColor(ring, numLeds, CRGB::Black, 0);
return;
}
switch(state->state) {
case STATE_SUNRISE:
// Sunrise animation sequence
if(currentMillis - state->startTime >= (FADE_TIME / 100)) {
state->startTime = currentMillis;
state->fadeProgress += 1;
if(state->fadeProgress <= DAWN_END) {
// Dawn
uint8_t brightness = map(state->fadeProgress, DAWN_START, DAWN_END, 0, 100);
setRingColorEnhanced(ring, numLeds, DAWN_COLOR, brightness, ringIndex);
}
else if(state->fadeProgress <= SUNRISE_END) {
// Sunrise
uint8_t brightness = map(state->fadeProgress, DAWN_END, SUNRISE_END, 100, 255);
// Blend between dawn and sunrise colors
float blendFactor = (float)(state->fadeProgress - DAWN_END) / (SUNRISE_END - DAWN_END);
CRGB blendedColor = blend(DAWN_COLOR, SUNRISE_COLOR, blendFactor * 255);
setRingColorEnhanced(ring, numLeds, blendedColor, brightness, ringIndex);
}
else if(state->fadeProgress <= SUNSET_START) {
// Full daylight
setRingColorEnhanced(ring, numLeds, DAYLIGHT, 255, ringIndex);
// Transition to full daylight state
if(state->fadeProgress == SUNRISE_END + 1) {
state->state = STATE_DAYLIGHT;
state->startTime = currentMillis;
}
}
}
break;
case STATE_DAYLIGHT:
// Hold full daylight for specified duration
setRingColorEnhanced(ring, numLeds, DAYLIGHT, 255, ringIndex);
if(currentMillis - state->startTime >= DAYLIGHT_DURATION) {
state->state = STATE_SUNSET;
state->fadeProgress = SUNSET_START;
state->startTime = currentMillis;
}
break;
case STATE_SUNSET:
// Continue the sunset sequence
if(currentMillis - state->startTime >= (FADE_TIME / 100)) {
state->startTime = currentMillis;
state->fadeProgress += 1;
if(state->fadeProgress <= SUNSET_PEAK) {
// Sunset transition - blend from daylight to sunset
uint8_t brightness = map(state->fadeProgress, SUNSET_START, SUNSET_PEAK, 255, 180);
// Blend between daylight and sunset colors
float blendFactor = (float)(state->fadeProgress - SUNSET_START) / (SUNSET_PEAK - SUNSET_START);
CRGB blendedColor = blend(DAYLIGHT, SUNSET_COLOR, blendFactor * 255);
setRingColorEnhanced(ring, numLeds, blendedColor, brightness, ringIndex);
}
else if(state->fadeProgress <= SUNSET_END) {
// Final sunset
uint8_t brightness = map(state->fadeProgress, SUNSET_PEAK, SUNSET_END, 180, 20);
setRingColorEnhanced(ring, numLeds, SUNSET_COLOR, brightness, ringIndex);
}
if(state->fadeProgress >= SUNSET_END) {
state->state = STATE_DARK1;
state->fadeProgress = 0;
state->startTime = currentMillis;
setRingColor(ring, numLeds, CRGB::Black, 0); // Turn off
}
}
break;
case STATE_DARK1:
// Hold dark for specified duration
if(currentMillis - state->startTime >= HOLD_TIME_DARK) {
state->state = STATE_MOONRISE;
state->fadeProgress = 0;
state->startTime = currentMillis;
} else {
setRingColor(ring, numLeds, CRGB::Black, 0); // Ensure it's off during hold
}
break;
case STATE_MOONRISE:
// Moonlight animation sequence
if(currentMillis - state->startTime >= (FADE_TIME / 100)) {
state->startTime = currentMillis;
state->fadeProgress += 1;
if(state->fadeProgress <= MOONRISE_END) {
// Moonrise
uint8_t brightness = map(state->fadeProgress, MOONRISE_START, MOONRISE_END, 0, 180);
setRingColorEnhanced(ring, numLeds, MOONLIGHT, brightness, ringIndex);
}
else if(state->fadeProgress <= MOONSET_START) {
// Full moonlight
setRingColorEnhanced(ring, numLeds, MOONLIGHT, 180, ringIndex);
// Transition to full moonlight hold state
if(state->fadeProgress == MOONRISE_END + 1) {
state->state = STATE_MOONLIGHT;
state->startTime = currentMillis;
}
}
}
break;
case STATE_MOONLIGHT:
// Hold full moonlight for specified duration
setRingColorEnhanced(ring, numLeds, MOONLIGHT, 180, ringIndex);
if(currentMillis - state->startTime >= MOONLIGHT_DURATION) {
state->state = STATE_MOONSET;
state->fadeProgress = MOONSET_START;
state->startTime = currentMillis;
}
break;
case STATE_MOONSET:
// Continue the moonset sequence
if(currentMillis - state->startTime >= (FADE_TIME / 100)) {
state->startTime = currentMillis;
state->fadeProgress += 1;
if(state->fadeProgress <= MOONSET_END) {
// Moonset
uint8_t brightness = map(state->fadeProgress, MOONSET_START, MOONSET_END, 180, 0);
setRingColorEnhanced(ring, numLeds, MOONLIGHT, brightness, ringIndex);
}
if(state->fadeProgress >= MOONSET_END) {
state->state = STATE_DARK2;
state->fadeProgress = 0;
state->startTime = currentMillis;
setRingColor(ring, numLeds, CRGB::Black, 0); // Turn off
}
}
break;
case STATE_DARK2:
// Hold dark before restart
if(currentMillis - state->startTime >= HOLD_TIME_DARK) {
state->state = STATE_SUNRISE;
state->fadeProgress = 0;
state->startTime = currentMillis;
} else {
setRingColor(ring, numLeds, CRGB::Black, 0); // Ensure it's off during hold
}
break;
}
}
// Check for button presses to change modes
void checkButtons(unsigned long currentMillis) {
// Only check periodically to avoid excessive reads
if(currentMillis - lastModeCheck >= MODE_CHECK_INTERVAL) {
lastModeCheck = currentMillis;
// Mode button - cycle through modes
if(digitalRead(MODE_PIN) == LOW) {
delay(50); // Debounce
if(digitalRead(MODE_PIN) == LOW) {
// Change mode and apply settings
currentMode = (SystemMode)((currentMode + 1) % MODE_COUNT);
applyModeSettings();
// Reset all rings to start a fresh cycle with new settings
for(int i = 0; i < 4; i++) {
ringStates[i].fadeProgress = 0;
ringStates[i].startTime = currentMillis;
ringStates[i].state = STATE_SUNRISE;
ringStates[i].active = (i == 0); // Only first ring active
}
// Display new mode
Serial.print(F("Mode changed to: "));
Serial.println(modeNames[currentMode]);
// Wait for button release
while(digitalRead(MODE_PIN) == LOW) {
delay(10);
}
}
}
}
}
void loop() {
unsigned long currentMillis = millis();
// Check for mode changes
checkButtons(currentMillis);
// Handle each ring
for(int i = 0; i < 4; i++) {
handleRing(i, currentMillis);
}
// Update all rings
FastLED.show();
}