// // WS2812 LED Analog Clock Firmware // Copyright (c) 2016-2018 jackw01 // NTP Changes: 2019 Commander1024 // This code is distrubuted under the MIT License, see LICENSE for details // #include #include #include #include #include #include #include #include #include #include #include "constants.h" // LED ring size CRGB leds[ledRingSize]; // Globals to keep track of state int clockMode, colorScheme; uint32_t lastLoopTime = 0; uint32_t lastButtonClickTime = 0; uint32_t lastDebugMessageTime = 0; uint8_t currentBrightness; uint8_t previousBrightness[16]; int lastSecondsValue = 0; uint32_t lastMillisecondsSetTime = 0; int milliseconds; void setup() { // Begin serial port Serial.begin(serialPortBaudRate); // Initialize Network and NTP if (Ethernet.begin (mac) == 0) { Serial.println ("Failed to configure Ethernet using DHCP"); // no point in carrying on, so do nothing forevermore: for (;;) ; } NTP.onNTPSyncEvent ([](NTPSyncEvent_t error) { if (error) { Serial.print ("Time Sync error: "); if (error == noResponse) Serial.println ("NTP server not reachable"); else if (error == invalidAddress) Serial.println ("Invalid NTP server address"); } else { Serial.print ("Got NTP time: "); Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ())); } }); NTP.setInterval (60, 900); NTP.setNTPTimeout (1000); NTP.begin (ntp_server, 1, true); // Init FastLED FastLED.addLeds(leds, ledRingSize); FastLED.setTemperature(Halogen); FastLED.show(); // Set button pin pinMode(pinButton, INPUT_PULLUP); // Read saved config from EEPROM colorScheme = EEPROM.read(eepromAddrColorScheme); clockMode = EEPROM.read(eepromAddrClockMode); // If button is pressed at startup, light all LEDs if (digitalRead(pinButton) == LOW) { for (int i = 0; i < ledRingSize; i++) leds[i] = white; FastLED.show(); delay(10000); } } static int i = 0; static int last = 0; void loop() { uint32_t currentTime = millis(); if (currentTime - lastLoopTime > runLoopIntervalMs) { lastLoopTime = currentTime; // Handle button if (digitalRead(pinButton) == LOW && currentTime - lastButtonClickTime > buttonClickRepeatDelayMs) { delay(buttonLongPressDelayMs); // Long press: clock mode, short press: color scheme if (digitalRead(pinButton) == LOW) { lastButtonClickTime = currentTime; colorScheme ++; if (colorScheme >= colorSchemeCount + 2) colorScheme = 0; // 2 special color schemes EEPROM.write(eepromAddrColorScheme, colorScheme); } else { clockMode ++; if (clockMode >= ClockModeCount) clockMode = 0; EEPROM.write(eepromAddrClockMode, clockMode); } } // Print debug message if (currentTime > lastDebugMessageTime + debugMessageIntervalMs) { lastDebugMessageTime = currentTime; printDebugMessage(); } // Update brightness - do a moving average to smooth out noisy potentiometers int sum = 0; for (uint8_t i = 15; i > 0; i--) { previousBrightness[i] = previousBrightness[i - 1]; sum += previousBrightness[i]; } previousBrightness[0] = map(analogRead(pinBrightness), 0, 1023, minBrightness, 255); sum += previousBrightness[0]; currentBrightness = sum / 16; FastLED.setBrightness(currentBrightness); // Get time and calculate milliseconds value that is synced with the RTC's second count int currentSeconds = second(now()); if (currentSeconds != lastSecondsValue) { lastSecondsValue = currentSeconds; milliseconds = 0; } currentTime = millis(); milliseconds = (milliseconds + currentTime - lastMillisecondsSetTime); lastMillisecondsSetTime = currentTime; // Show clock clearLeds(); showClock(); // Check/Renew DHCP Ethernet.maintain (); } } // Display the current clock void showClock() { switch (clockMode) { case ClockModeRingClock: drawRingClock(); break; case ClockModeDotClock: drawDotClock(); break; case ClockModeDotClockTrail: drawDotClockTrail(); break; case ClockModeDotClockGlow: drawDotClockGlow(); break; } } // Print debugging info over serial void printDebugMessage() { Serial.print("Current date/time: "); Serial.print(year(now()), DEC); Serial.print("/"); Serial.print(month(now()), DEC); Serial.print("/"); Serial.print(day(now()), DEC); Serial.print(" "); Serial.print(hour(now()), DEC); Serial.print(":"); Serial.print(minute(now()), DEC); Serial.print(":"); Serial.print(second(now()), DEC); Serial.println(); Serial.print("Display mode: "); Serial.println(clockMode); Serial.print("Color scheme: "); Serial.println(colorScheme); Serial.print("Brightness: "); Serial.println(currentBrightness); Serial.println(""); } // Show a ring clock void drawRingClock() { int h = hourPosition(); int m = minutePosition(); float s = secondPosition(); if (m > h) { for (int i = 0; i < m; i++) setLed(i, minuteColor(), BlendModeOver, 1.0); for (int i = 0; i < h; i++) setLed(i, hourColor(), BlendModeOver, 1.0); } else { for (int i = 0; i < h; i++) setLed(i, hourColor(), BlendModeOver, 1.0); for (int i = 0; i < m; i++) setLed(i, minuteColor(), BlendModeOver, 1.0); } if (showSecondHand) setLed(s, secondColor(), BlendModeAlpha, 1.0); FastLED.show(); } // Show a more traditional dot clock void drawDotClock() { float h = hourPosition(); float m = minutePosition(); float s = secondPosition(); for (float i = h - 1.0; i < h + 2.0; i++) setLed(i, hourColor(), BlendModeAlpha, 1.0); setLed(m, minuteColor(), BlendModeAlpha, 1.0); if (showSecondHand) setLed(s, secondColor(), BlendModeAlpha, 1.0); FastLED.show(); } // Show a dot clock where the hands have a glowing trail behing them void drawDotClockTrail() { float h = hourPosition(); float m = minutePosition(); float s = secondPosition(); for (float i = -hourTrailLength; i < 1.0; i++) setLed(h + i, hourColor(), BlendModeAdd, mapFloat(i, -hourTrailLength, 1.0, 0.1, 1.0)); for (float i = -minuteTrailLength; i < 1.0; i++) setLed(m + i, minuteColor(), BlendModeAdd, mapFloat(i, -minuteTrailLength, 1.0, 0.1, 1.0)); if (showSecondHand) { for (float i = -secondTrailLength; i < 1.0; i++) setLed(s + i, secondColor(), BlendModeAdd, mapFloat(i, -secondTrailLength, 1.0, 0.1, 1.0)); } FastLED.show(); } // Show a dot clock where the hands glow outwards from their position void drawDotClockGlow() { float h = hourPosition(); float m = minutePosition(); float s = secondPosition(); for (float i = h - hourGlowWidth; i <= h + hourGlowWidth; i++) { setLed(i, hourColor(), BlendModeAdd, mapFloat(fabs(h - i), 0.0, hourGlowWidth, 1.0, 0.1)); } for (float i = m - minuteGlowWidth; i <= m + minuteGlowWidth; i++) { setLed(i, minuteColor(), BlendModeAdd, mapFloat(fabs(m - i), 0.0, minuteGlowWidth, 1.0, 0.1)); } if (showSecondHand) { for (float i = s - secondGlowWidth; i <= s + secondGlowWidth; i++) { setLed(i, secondColor(), BlendModeAdd, mapFloat(fabs(s - i), 0.0, secondGlowWidth, 1.0, 0.1)); } } FastLED.show(); } // Get floating point hour representation float floatHour() { return (float)hour(now()) + mapFloat(minute(now()) + mapFloat(second(now()), 0.0, 59.0, 0.0, 1.0), 0.0, 59.0, 0.0, 1.0); } // Get positions mapped to ring size float hourPosition() { if (twelveHour) { int hourt; if (hour(now()) > 12) hourt = (hour(now()) - 12) * (ledRingSize / 12); else hourt = hour(now()) * (ledRingSize / 12); return hourt + mapFloat(minute(now()) + 0.001, 0.0, 59.0, 0.0, (ledRingSize / 12.0) - 1.0); } else { int hourt = hour(now()) * (ledRingSize / 24); return hourt + mapFloat(minute(now()) + 0.001, 0, 59, 0, (ledRingSize / 24.0) - 1.0); } } float minutePosition() { return mapFloat( (float)minute(now()) + ((0.001 + 1.0 / 60.0) * (float)second(now())), 0.0, 59.0, 0.0, (float)ledRingSize ); } float secondPosition() { return mapFloat( second(now()) + (0.001 * milliseconds), 0.0, 60.0, 0.0, (float)ledRingSize ); } // Get colors CRGB hourColor() { if (colorScheme < colorSchemeCount) return colorSchemes[colorScheme][0]; else if (colorScheme == colorSchemeCount + 0) { return CHSV(map(hour(now()), 0, 24, 0, 255), 255, 255); } else if (colorScheme == colorSchemeCount + 1) { return CHSV((uint8_t)mapFloat(fmod(20.0 - floatHour(), 24.0), 0.0, 24.0, 0.0, 255.0), 255, 255); } } CRGB minuteColor() { if (colorScheme < colorSchemeCount) return colorSchemes[colorScheme][1]; else if (colorScheme == colorSchemeCount + 0) { return CHSV(map(minute(now()), 0, 59, 0, 255), 255, 255); } else if (colorScheme == colorSchemeCount + 1) { return CHSV((uint8_t)mapFloat(fmod(20.0 - floatHour(), 24.0), 0.0, 24.0, 0.0, 255.0), 255, 255); } } CRGB secondColor() { if (colorScheme < colorSchemeCount) return colorSchemes[colorScheme][2]; else if (colorScheme == colorSchemeCount + 0) { return CHSV(map(second(now()), 0, 59, 0, 255), 255, 255); } else if (colorScheme == colorSchemeCount + 1) { return CHSV((uint8_t)mapFloat(fmod(20.0 - floatHour(), 24.0), 0.0, 24.0, 0.0, 255.0), 255, 255); } } // Clear the LED ring void clearLeds() { for (int i = 0; i < ledRingSize; i++) leds[i] = CRGB(0, 0, 0); } // Set LED(s) at a position with enhanced rendering void setLed(float position, CRGB color, BlendMode blendMode, float factor) { if (useEnhancedRenderer) { int low = floor(position); int high = ceil(position); float lowFactor = ((float)high - position); float highFactor = (position - (float)low); if (blendMode == BlendModeAdd) { blendAdd(wrap(low), color, lowFactor * factor); blendAdd(wrap(high), color, highFactor * factor); } else if (blendMode == BlendModeAlpha) { blendAlpha(wrap(low), color, lowFactor * factor); blendAlpha(wrap(high), color, highFactor * factor); } else if (blendMode == BlendModeOver) { blendOver(wrap(low), color, lowFactor * factor); blendOver(wrap(high), color, highFactor * factor); } } else { leds[wrap((int)position)] = color; } } // Additive blending void blendAdd(int position, CRGB color, float factor) { leds[position].r += min(color.r * factor, 255 - leds[position].r); leds[position].g += min(color.g * factor, 255 - leds[position].g); leds[position].b += min(color.b * factor, 255 - leds[position].b); } // Alpha blending (factor is the alpha value) void blendAlpha(int position, CRGB color, float factor) { leds[position].r = (uint8_t)mapFloat(factor, 0.0, 1.0, leds[position].r, color.r); leds[position].g = (uint8_t)mapFloat(factor, 0.0, 1.0, leds[position].g, color.g); leds[position].b = (uint8_t)mapFloat(factor, 0.0, 1.0, leds[position].b, color.b); } // Overlay/replace blending void blendOver(int position, CRGB color, float factor) { leds[position].r = color.r * factor; leds[position].g = color.g * factor; leds[position].b = color.b * factor; leds[position] = color; } // Wrap around LED ring int wrap(int i) { if (i >= ledRingSize) return i - ledRingSize; else if (i < 0) return ledRingSize + i; else return i; } // Because Arduino does not float mapFloat(float x, float inMin, float inMax, float outMin, float outMax) { return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; }