#include <Adafruit_NeoPixel.h>
// Pin definitions for each ring
#define RING1_PIN 2 // 1-LED ring
#define RING2_PIN 3 // 8-LED ring
#define RING3_PIN 4 // 12-LED ring
#define RING4_PIN 5 // 16-LED ring
// Define the number of LEDs in each ring
#define RING1_COUNT 1
#define RING2_COUNT 8
#define RING3_COUNT 12
#define RING4_COUNT 16
// Time constants
#define HOUR_DURATION 30000 // Each hour lasts 30 seconds (in milliseconds)
#define DAY_DURATION (24 * HOUR_DURATION)
// Create NeoPixel objects for each ring
Adafruit_NeoPixel ring1(RING1_COUNT, RING1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring2(RING2_COUNT, RING2_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring3(RING3_COUNT, RING3_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring4(RING4_COUNT, RING4_PIN, NEO_GRB + NEO_KHZ800);
// Time tracking variables
unsigned long startTime;
unsigned long currentMillis;
unsigned long elapsedMillis;
int currentHour;
float hourProgress;
// Cloud simulation
unsigned long nextCloudTime = 0;
bool cloudActive = false;
int cloudDuration = 0;
float cloudIntensity = 0.0;
// Current color tracking
uint32_t currentColor = 0; // Black
void setup() {
// Initialize all rings
ring1.begin();
ring2.begin();
ring3.begin();
ring4.begin();
// Set consistent brightness for all rings
ring1.setBrightness(150);
ring2.setBrightness(150);
ring3.setBrightness(150);
ring4.setBrightness(150);
// Initialize all pixels to 'off'
setAllRingsColor(0, 0, 0);
// Initialize random seed
randomSeed(analogRead(0));
// Record start time
startTime = millis();
// For debugging
Serial.begin(9600);
Serial.println("Sunrise/Sunset and Moon Phase Simulator Started");
}
void loop() {
// Get current time information
currentMillis = millis();
elapsedMillis = (currentMillis - startTime) % DAY_DURATION;
// Calculate current hour (0-23) and progress within that hour (0.0-1.0)
currentHour = elapsedMillis / HOUR_DURATION;
hourProgress = (elapsedMillis % HOUR_DURATION) / (float)HOUR_DURATION;
// Update colors based on time of day
updateDayNightCycle();
// Handle cloud effects
updateClouds();
// Output debug info occasionally
if (random(100) == 0) {
Serial.print("Hour: ");
Serial.print(currentHour);
Serial.print(", Progress: ");
Serial.println(hourProgress);
}
delay(20); // Small delay to prevent overwhelming the LED update
}
void updateDayNightCycle() {
uint32_t targetColor;
// Determine color based on time of day
// Sunrise: Hours 5-7
if (currentHour >= 5 && currentHour < 7) {
float sunriseProgress;
if (currentHour == 5) {
sunriseProgress = hourProgress * 0.5; // First half of sunrise
} else { // Hour 6
sunriseProgress = 0.5 + (hourProgress * 0.5); // Second half of sunrise
}
targetColor = getSunriseColor(sunriseProgress);
}
// Daytime: Hours 7-17
else if (currentHour >= 7 && currentHour < 17) {
float daytimeProgress = (currentHour - 7 + hourProgress) / 10.0;
targetColor = getDaylightColor(daytimeProgress);
}
// Sunset: Hours 17-19
else if (currentHour >= 17 && currentHour < 19) {
float sunsetProgress;
if (currentHour == 17) {
sunsetProgress = hourProgress * 0.5; // First half of sunset
} else { // Hour 18
sunsetProgress = 0.5 + (hourProgress * 0.5); // Second half of sunset
}
targetColor = getSunsetColor(sunsetProgress);
}
// Night with moonlight: Hours 19-5
else {
float nightProgress;
if (currentHour >= 19) {
nightProgress = (currentHour - 19 + hourProgress) / 10.0; // From 19-23
} else {
nightProgress = (currentHour + 5 + hourProgress) / 10.0; // From 0-4
}
targetColor = getMoonlightColor(nightProgress);
}
// Extract RGB components
uint8_t r = (targetColor >> 16) & 0xFF;
uint8_t g = (targetColor >> 8) & 0xFF;
uint8_t b = targetColor & 0xFF;
// Apply color to all rings
setAllRingsColor(r, g, b);
// Store current color for reference
currentColor = targetColor;
}
// Sunrise colors: Dark purple → Indigo → Blue → Orange → Yellow → Bright white
uint32_t getSunriseColor(float progress) {
uint8_t r, g, b;
if (progress < 0.2) {
// Dark purple to indigo
float localProgress = progress / 0.2;
r = map(localProgress, 0, 1, 10, 75);
g = map(localProgress, 0, 1, 0, 0);
b = map(localProgress, 0, 1, 20, 130);
}
else if (progress < 0.4) {
// Indigo to blue
float localProgress = (progress - 0.2) / 0.2;
r = map(localProgress, 0, 1, 75, 20);
g = map(localProgress, 0, 1, 0, 20);
b = map(localProgress, 0, 1, 130, 200);
}
else if (progress < 0.6) {
// Blue to light blue
float localProgress = (progress - 0.4) / 0.2;
r = map(localProgress, 0, 1, 20, 120);
g = map(localProgress, 0, 1, 20, 150);
b = map(localProgress, 0, 1, 200, 255);
}
else if (progress < 0.8) {
// Light blue to orange-yellow
float localProgress = (progress - 0.6) / 0.2;
r = map(localProgress, 0, 1, 120, 255);
g = map(localProgress, 0, 1, 150, 200);
b = map(localProgress, 0, 1, 255, 100);
}
else {
// Orange-yellow to bright daylight
float localProgress = (progress - 0.8) / 0.2;
r = map(localProgress, 0, 1, 255, 255);
g = map(localProgress, 0, 1, 200, 255);
b = map(localProgress, 0, 1, 100, 240);
}
return (uint32_t)((uint32_t)r << 16 | (uint32_t)g << 8 | b);
}
// Daylight colors: Bright midday with slight variations
uint32_t getDaylightColor(float progress) {
uint8_t r, g, b;
// Create a subtle curve to simulate changing light during the day
// Brightest at midday (progress = 0.5)
float brightness = 1.0 - abs(progress - 0.5) * 0.2;
r = 255 * brightness;
g = 255 * brightness;
b = 240 * brightness;
// Add a slight color temperature shift (warmer at beginning/end, cooler at midday)
float colorTemp = abs(progress - 0.5); // 0 at midday, 0.5 at beginning/end
r += colorTemp * 0; // Red stays relatively constant
g -= colorTemp * 20; // Green reduces slightly at beginning/end
b -= colorTemp * 50; // Blue reduces more at beginning/end (warmer)
// Constrain values to valid range
r = constrain(r, 0, 255);
g = constrain(g, 0, 255);
b = constrain(b, 0, 255);
return (uint32_t)((uint32_t)r << 16 | (uint32_t)g << 8 | b);
}
// Sunset colors: Bright yellow → Orange → Deep red → Purple → Dark blue
uint32_t getSunsetColor(float progress) {
uint8_t r, g, b;
if (progress < 0.2) {
// Bright daylight to yellow
float localProgress = progress / 0.2;
r = map(localProgress, 0, 1, 255, 255);
g = map(localProgress, 0, 1, 255, 200);
b = map(localProgress, 0, 1, 240, 100);
}
else if (progress < 0.4) {
// Yellow to orange
float localProgress = (progress - 0.2) / 0.2;
r = map(localProgress, 0, 1, 255, 255);
g = map(localProgress, 0, 1, 200, 140);
b = map(localProgress, 0, 1, 100, 0);
}
else if (progress < 0.6) {
// Orange to deep red
float localProgress = (progress - 0.4) / 0.2;
r = map(localProgress, 0, 1, 255, 200);
g = map(localProgress, 0, 1, 140, 20);
b = map(localProgress, 0, 1, 0, 0);
}
else if (progress < 0.8) {
// Deep red to purple
float localProgress = (progress - 0.6) / 0.2;
r = map(localProgress, 0, 1, 200, 80);
g = map(localProgress, 0, 1, 20, 0);
b = map(localProgress, 0, 1, 0, 80);
}
else {
// Purple to dark blue (almost black)
float localProgress = (progress - 0.8) / 0.2;
r = map(localProgress, 0, 1, 80, 0);
g = map(localProgress, 0, 1, 0, 0);
b = map(localProgress, 0, 1, 80, 20);
}
return (uint32_t)((uint32_t)r << 16 | (uint32_t)g << 8 | b);
}
// Moonlight colors based on moon phase
uint32_t getMoonlightColor(float progress) {
uint8_t r, g, b;
// No moon (new moon) at start and end of night cycle
// Full moon in the middle (progress = 0.5)
float moonPhase = 1.0 - abs(progress - 0.5) * 2.0; // 0 at start/end, 1 at middle
// Determine color based on phase - bluish for crescent, white for full
r = 50 + moonPhase * 50; // More red as moon gets fuller
g = 70 + moonPhase * 50; // More green as moon gets fuller
b = 100 + moonPhase * 70; // More blue as moon gets fuller
// Brief delay after sunset before moonrise (if early in night cycle)
if (progress < 0.1) {
float delayFactor = (0.1 - progress) / 0.1;
r = r * (1.0 - delayFactor);
g = g * (1.0 - delayFactor);
b = b * (1.0 - delayFactor);
}
return (uint32_t)((uint32_t)r << 16 | (uint32_t)g << 8 | b);
}
void updateClouds() {
// Check if it's time to start a new cloud event
if (currentMillis > nextCloudTime) {
// Determine if we should have clouds based on time of day
bool daytime = (currentHour >= 7 && currentHour < 19);
// 30% chance of clouds during daytime, 15% chance at night
int cloudChance = daytime ? 30 : 15;
if (random(100) < cloudChance) {
// Start cloud event
cloudActive = true;
// Random cloud duration between 2-10 seconds
cloudDuration = random(2000, 10000);
// Random cloud intensity (stronger during day)
cloudIntensity = daytime ? random(20, 60) / 100.0 : random(10, 30) / 100.0;
// Apply cloud effect
applyCloudEffect();
}
// Set next cloud check time (5-20 seconds from now)
nextCloudTime = currentMillis + random(5000, 20000);
}
// If cloud is active and duration has passed, end it
if (cloudActive && (currentMillis > nextCloudTime - cloudDuration)) {
cloudActive = false;
// Remove cloud effect (restore normal lighting)
updateDayNightCycle();
}
}
void applyCloudEffect() {
// Get the current base color (without clouds)
uint8_t baseR = (currentColor >> 16) & 0xFF;
uint8_t baseG = (currentColor >> 8) & 0xFF;
uint8_t baseB = currentColor & 0xFF;
// Calculate dimmed color for cloud effect
uint8_t cloudR = baseR * (1.0 - cloudIntensity);
uint8_t cloudG = baseG * (1.0 - cloudIntensity);
uint8_t cloudB = baseB * (1.0 - cloudIntensity);
// Apply cloud effect differently to each ring for more realistic look
// Central ring (1 LED) - least affected
float ring1Intensity = cloudIntensity * 0.5; // 50% of the cloud effect
uint8_t r1 = baseR - (baseR - cloudR) * ring1Intensity;
uint8_t g1 = baseG - (baseG - cloudG) * ring1Intensity;
uint8_t b1 = baseB - (baseB - cloudB) * ring1Intensity;
setRing1Color(r1, g1, b1);
// Second ring (8 LEDs) - slightly more affected
applyCloudToRing(ring2, RING2_COUNT, baseR, baseG, baseB, cloudR, cloudG, cloudB, cloudIntensity * 0.7);
// Third ring (12 LEDs) - more affected
applyCloudToRing(ring3, RING3_COUNT, baseR, baseG, baseB, cloudR, cloudG, cloudB, cloudIntensity * 0.85);
// Fourth ring (16 LEDs) - most affected
applyCloudToRing(ring4, RING4_COUNT, baseR, baseG, baseB, cloudR, cloudG, cloudB, cloudIntensity);
}
void applyCloudToRing(Adafruit_NeoPixel &ring, int ledCount,
uint8_t baseR, uint8_t baseG, uint8_t baseB,
uint8_t cloudR, uint8_t cloudG, uint8_t cloudB,
float baseIntensity) {
for (int i = 0; i < ledCount; i++) {
// Vary cloud intensity slightly for each LED to create more natural effect
float localIntensity = baseIntensity * (0.9 + random(20) / 100.0);
uint8_t r = baseR - (baseR - cloudR) * localIntensity;
uint8_t g = baseG - (baseG - cloudG) * localIntensity;
uint8_t b = baseB - (baseB - cloudB) * localIntensity;
ring.setPixelColor(i, ring.Color(r, g, b));
}
ring.show();
}
// Utility function to map float values to integer
int map(float x, float in_min, float in_max, float out_min, float out_max) {
return (int)((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}
// Set color to all LEDs in Ring 1
void setRing1Color(uint8_t r, uint8_t g, uint8_t b) {
for (int i = 0; i < RING1_COUNT; i++) {
ring1.setPixelColor(i, ring1.Color(r, g, b));
}
ring1.show();
}
// Set the same color to all rings
void setAllRingsColor(uint8_t r, uint8_t g, uint8_t b) {
// Set color for Ring 1 (1 LED)
for (int i = 0; i < RING1_COUNT; i++) {
ring1.setPixelColor(i, ring1.Color(r, g, b));
}
ring1.show();
// Set color for Ring 2 (8 LEDs)
for (int i = 0; i < RING2_COUNT; i++) {
ring2.setPixelColor(i, ring2.Color(r, g, b));
}
ring2.show();
// Set color for Ring 3 (12 LEDs)
for (int i = 0; i < RING3_COUNT; i++) {
ring3.setPixelColor(i, ring3.Color(r, g, b));
}
ring3.show();
// Set color for Ring 4 (16 LEDs)
for (int i = 0; i < RING4_COUNT; i++) {
ring4.setPixelColor(i, ring4.Color(r, g, b));
}
ring4.show();
}