370 lines
11 KiB
C++
370 lines
11 KiB
C++
//
|
|
// 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 <math.h>
|
|
#include <FastLED.h>
|
|
#include <EEPROM.h>
|
|
#include <TimeLib.h>
|
|
#include <NtpClientLib.h>
|
|
#include <SPI.h>
|
|
#include <EthernetUdp.h>
|
|
#include <Ethernet.h>
|
|
#include <Dns.h>
|
|
#include <Dhcp.h>
|
|
|
|
#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<NEOPIXEL, pinLeds>(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;
|
|
}
|