#include #include #include #include #include #include // ================= LED ================= #define PIN D4 #define NUMPIXELS 110 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); // ================= SERVER & NTP ================= ESP8266WebServer server(80); String ntpServer = "de.pool.ntp.org"; String tzString = "CET-1CEST,M3.5.0/02,M10.5.0/03"; // ================= SETTINGS ================= struct Settings { int brightness; int nightBrightness; bool autoNight; int nightStart; int nightEnd; uint32_t color; uint32_t bgColor; bool backgroundMode; char ntpServer[64]; char tzString[64]; bool nightBackgroundDisabled; // Neu } settings; // ================= VAR ================= uint32_t currentColor = 0x00FFFF; uint32_t lastColor = 0x00FFFF; uint32_t bgColor = 0x000000; bool backgroundMode = false; bool nightBackgroundDisabled = false; // Neu bool forceInstant = false; bool updateNeeded = true; int brightness = 120; int nightBrightness = 10; bool autoNight = true; int nightStart = 22; int nightEnd = 6; uint32_t targetBuffer[NUMPIXELS]; uint32_t currentBuffer[NUMPIXELS]; int lastMinute = -1; int lastH = -1; // ================= EEPROM ================= void saveSettings(){ settings.brightness = brightness; settings.nightBrightness = nightBrightness; settings.autoNight = autoNight; settings.nightStart = nightStart; settings.nightEnd = nightEnd; settings.color = currentColor; settings.bgColor = bgColor; settings.backgroundMode = backgroundMode; settings.nightBackgroundDisabled = nightBackgroundDisabled; // Neu strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer)); strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString)); EEPROM.put(0, settings); EEPROM.commit(); } void loadSettings(){ EEPROM.begin(512); EEPROM.get(0, settings); if(settings.brightness == 0 || settings.brightness == 255){ brightness = 120; nightBrightness = 10; autoNight = true; nightStart = 22; nightEnd = 6; currentColor = 0x00FFFF; bgColor = 0x000000; backgroundMode = false; nightBackgroundDisabled = false; ntpServer = "de.pool.ntp.org"; tzString = "CET-1CEST,M3.5.0/02,M10.5.0/03"; } else { brightness = settings.brightness; nightBrightness = settings.nightBrightness; autoNight = settings.autoNight; nightStart = settings.nightStart; nightEnd = settings.nightEnd; currentColor = settings.color; bgColor = settings.bgColor; backgroundMode = settings.backgroundMode; nightBackgroundDisabled = settings.nightBackgroundDisabled; ntpServer = String(settings.ntpServer); tzString = String(settings.tzString); } configTime(tzString.c_str(), ntpServer.c_str()); } // ================= HELFER ================= bool isNightTime(struct tm *t){ if(!autoNight) return false; int h = t->tm_hour; if(nightStart > nightEnd){ return (h >= nightStart || h < nightEnd); } else { return (h >= nightStart && h < nightEnd); } } void setRange(int a,int b){ for(int i=a;i<=b;i++) targetBuffer[i]=currentColor; } uint32_t blend(uint32_t c1, uint32_t c2, float t){ uint8_t r1=(c1>>16)&255, g1=(c1>>8)&255, b1=c1&255; uint8_t r2=(c2>>16)&255, g2=(c2>>8)&255, b2=c2&255; return pixels.Color(r1+(r2-r1)*t, g1+(g2-g1)*t, b1+(b2-b1)*t); } uint8_t gamma8(uint8_t x) { if (x == 0) return 0; // Verwende einen leicht angepassten Exponenten für den Nachtmodus float f = (float)x / 255.0; return (uint8_t)(pow(f, 2.0) * 255.0 + 0.5); } uint32_t applyGammaBrightness(uint32_t c, float brightnessFactor) { // Wenn der Faktor extrem klein ist, erzwingen wir eine Mindesthelligkeit für aktive LEDs if (brightnessFactor < 0.01 && brightnessFactor > 0) brightnessFactor = 0.01; uint8_t r = (uint8_t)(((c >> 16) & 255) * brightnessFactor); uint8_t g = (uint8_t)(((c >> 8) & 255) * brightnessFactor); uint8_t b = (uint8_t)((c & 255) * brightnessFactor); return pixels.Color(gamma8(r), gamma8(g), gamma8(b)); } int getBrightness(struct tm *t){ return isNightTime(t) ? nightBrightness : brightness; } void showInstant(){ time_t now = time(nullptr); struct tm *t = localtime(&now); float b = getBrightness(t) / 255.0; for(int i=0;i=25) h++; String arr[]={"Zwölf","Eins","Zwei","Drei","Vier","Fünf","Sechs","Sieben","Acht","Neun","Zehn","Elf"}; String hT = arr[h%12]; if(m5==0 && h%12==1) hT = "ein"; String t = "Es ist "; switch(m5){ case 0: t+= hT + " Uhr"; break; case 5: t+= "fünf nach " + arr[h%12]; break; case 10: t+= "zehn nach " + arr[h%12]; break; case 15: t+= "viertel nach " + arr[h%12]; break; case 20: t+= "zwanzig nach " + arr[h%12]; break; case 25: t+= "fünf vor halb " + arr[h%12]; break; case 30: t+= "halb " + arr[h%12]; break; case 35: t+= "fünf nach halb " + arr[h%12]; break; case 40: t+= "zwanzig vor " + arr[h%12]; break; case 45: t+= "viertel vor " + arr[h%12]; break; case 50: t+= "zehn vor " + arr[h%12]; break; case 55: t+= "fünf vor " + arr[h%12]; break; } return t; } void showTimeWords(int h, int m){ time_t now = time(nullptr); struct tm *t = localtime(&now); for(int i=0;i= 25) h++; switch(m5){ case 0: word_UHR(); break; case 5: word_FUENF_M(); word_NACH(); break; case 10: word_ZEHN_M(); word_NACH(); break; case 15: word_VIERTEL(); word_NACH(); break; case 20: word_ZWANZIG(); word_NACH(); break; case 25: word_FUENF_M(); word_VOR(); word_HALB(); break; case 30: word_HALB(); break; case 35: word_FUENF_M(); word_NACH(); word_HALB(); break; case 40: word_ZWANZIG(); word_VOR(); break; case 45: word_VIERTEL(); word_VOR(); break; case 50: word_ZEHN_M(); word_VOR(); break; case 55: word_FUENF_M(); word_VOR(); break; } int hh = h % 12; if(hh == 1 && full) word_EIN(); else if(hh == 1) word_EINS(); else if(hh == 2) word_ZWEI(); else if(hh == 3) word_DREI(); else if(hh == 4) word_VIER(); else if(hh == 5) word_FUENF_H(); else if(hh == 6) word_SECHS(); else if(hh == 7) word_SIEBEN(); else if(hh == 8) word_ACHT(); else if(hh == 9) word_NEUN(); else if(hh == 10) word_ZEHN_H(); else if(hh == 11) word_ELF(); else word_ZWOELF(); if(forceInstant){ showInstant(); forceInstant = false; } else { fadeToTargetSmooth(20, 10); } } // ================= WEB HANDLERS ================= void handleStatus() { time_t now = time(nullptr); struct tm *t = localtime(&now); char hexCol[10], hexBg[10]; snprintf(hexCol, sizeof(hexCol), "#%06X", currentColor); snprintf(hexBg, sizeof(hexBg), "#%06X", bgColor); String ntpStat = (now > 100000) ? "OK" : "SYNC..."; String json = "{"; json += "\"txt\":\"" + getTimeText(t->tm_hour, t->tm_min) + "\","; json += "\"ip\":\"" + WiFi.localIP().toString() + "\","; json += "\"ntp\":\"" + ntpStat + "\","; json += "\"ntpServer\":\"" + ntpServer + "\","; json += "\"tzString\":\"" + tzString + "\","; json += "\"brightness\":" + String(brightness) + ","; json += "\"nightBrightness\":" + String(nightBrightness) + ","; json += "\"color\":\"" + String(hexCol) + "\","; json += "\"bgColor\":\"" + String(hexBg) + "\","; json += "\"night\":" + String(autoNight ? 1 : 0) + ","; json += "\"nightStart\":" + String(nightStart) + ","; json += "\"nightEnd\":" + String(nightEnd) + ","; json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ","; json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0); json += "}"; server.send(200, "application/json", json); } void handleRoot(){ String html = R"rawliteral(
Lade...
Verbinde...
 
Hintergrund
 
Nachtmodus
 
Von: Uhr Bis: Uhr
 

HG in Nacht aus
)rawliteral"; server.send(200, "text/html", html); } void handleColor(){ if(server.hasArg("c")){ currentColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant=true; showTimeWords(lastH, lastMinute); } server.send(200); } void handleBGColor(){ if(server.hasArg("c")){ bgColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant=true; updateNeeded=true; } server.send(200); } void handleBrightness(){ brightness=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNight(){ autoNight=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNightStart(){ nightStart=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNightEnd(){ nightEnd=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNightBrightness(){ nightBrightness=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNightBgOff(){ nightBackgroundDisabled=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleBG(){ backgroundMode=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); } void handleNTP(){ ntpServer=server.arg("val"); configTime(tzString.c_str(), ntpServer.c_str()); server.send(200); } void handleTZ(){ tzString=server.arg("val"); configTime(tzString.c_str(), ntpServer.c_str()); server.send(200); } void handleSave(){ saveSettings(); server.send(200); } // ================= SETUP & LOOP ================= void setup(){ Serial.begin(115200); WiFiManager wm; wm.autoConnect("Wortuhr"); loadSettings(); server.on("/", handleRoot); server.on("/status", handleStatus); server.on("/color", handleColor); server.on("/bgcolor", handleBGColor); server.on("/brightness", handleBrightness); server.on("/night", handleNight); server.on("/nightStart", handleNightStart); server.on("/nightEnd", handleNightEnd); server.on("/nightBrightness", handleNightBrightness); server.on("/nightBgOff", handleNightBgOff); server.on("/bg", handleBG); server.on("/setNTP", handleNTP); server.on("/setTZ", handleTZ); server.on("/save", handleSave); server.begin(); pixels.begin(); } void loop(){ server.handleClient(); time_t now = time(nullptr); struct tm *t = localtime(&now); if(t) { int currentM5 = (t->tm_min / 5) * 5; if(currentM5 != lastMinute || t->tm_hour != lastH){ lastMinute = currentM5; lastH = t->tm_hour; updateNeeded = true; } } if(updateNeeded){ showTimeWords(lastH, (t ? t->tm_min : 0)); updateNeeded = false; } }