#include #include #include #include #include #include // ================= LED ================= #define PIN D4 #define NUMPIXELS 110 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); // ================= SERVER ================= ESP8266WebServer server(80); // ================= NTP ================= 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]; } settings; // ================= VAR ================= uint32_t currentColor = 0x00FFFF; uint32_t lastColor = 0x00FFFF; uint32_t bgColor = 0x000000; bool backgroundMode = false; 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; // ================= 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; 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); // Prüfen, ob EEPROM leer ist (über Helligkeit) if(settings.brightness == 0 || settings.brightness == 255){ // Standardwerte setzen brightness = 120; nightBrightness = 10; autoNight = true; nightStart = 22; nightEnd = 6; currentColor = 0x00FFFF; bgColor = 0x000000; backgroundMode = 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; ntpServer = String(settings.ntpServer); tzString = String(settings.tzString); } // Zeitkonfiguration sofort anwenden configTime(tzString.c_str(), ntpServer.c_str()); } // ================= HELFER ================= void clearTarget(){ for(int i=0;i>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 ); } uint32_t applyBrightness(uint32_t c, float brightnessFactor){ uint8_t r = ((c >> 16) & 255) * brightnessFactor; uint8_t g = ((c >> 8) & 255) * brightnessFactor; uint8_t b = (c & 255) * brightnessFactor; return pixels.Color(r,g,b); } uint8_t gamma8(uint8_t x){ if(x<5) return 0; float f = x / 255.0; f = pow(f, 2.2); // Gamma 2.2 return (uint8_t)(f * 255); } uint32_t applyGammaBrightness(uint32_t c, float brightnessFactor){ uint8_t r = ((c >> 16) & 255) * brightnessFactor; uint8_t g = ((c >> 8) & 255) * brightnessFactor; uint8_t b = (c & 255) * brightnessFactor; // 🔥 Gamma Korrektur r = gamma8(r); g = gamma8(g); b = gamma8(b); return pixels.Color(r,g,b); } void updateDisplayNow(){ time_t now = time(nullptr); struct tm *t = localtime(&now); if(t){ showTimeWords(t->tm_hour, t->tm_min); } } 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 t="Es ist "; switch(m5){ case 0: t+= hourText(h, true)+" Uhr"; break; case 5: t+="fünf nach "+hourToText(h); break; case 10: t+="zehn nach "+hourToText(h); break; case 15: t+="viertel nach "+hourToText(h); break; case 20: t+="zwanzig nach "+hourToText(h); break; case 25: t+="fünf vor halb "+hourToText(h); break; case 30: t+="halb "+hourToText(h); break; case 35: t+="fünf nach halb "+hourToText(h); break; case 40: t+="zwanzig vor "+hourToText(h); break; case 45: t+="viertel vor "+hourToText(h); break; case 50: t+="zehn vor "+hourToText(h); break; case 55: t+="fünf vor "+hourToText(h); break; } return t; } // ================= NACHTMODUS ================= int getBrightness(struct tm *t){ if(!autoNight) return brightness; int h=t->tm_hour; if(nightStart > nightEnd){ if(h >= nightStart || h < nightEnd) return nightBrightness; } else { if(h >= nightStart && h < nightEnd) return nightBrightness; } return brightness; } void fadeToTargetSmooth(int steps = 10, int delayMs = 5){ time_t now = time(nullptr); struct tm *t = localtime(&now); float bTarget = getBrightness(t) / 255.0; for(int s = 0; s <= steps; s++){ float tBlend = (float)s / steps; for(int i=0;i= 25) h++; // Minuten 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; } // Stunden 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(); // 🔥 jetzt alles weich darstellen (inkl. Gamma & Brightness) if(forceInstant){ showInstant(); forceInstant = false; }else{ fadeToTargetSmooth(10,5); } } // ================= WEB ================= void handleNTP() { if (server.hasArg("val")) { ntpServer = server.arg("val"); // Konfiguration sofort mit neuem Server aktualisieren configTime(tzString.c_str(), ntpServer.c_str()); Serial.println("NTP Server geändert: " + ntpServer); } server.send(200, "text/plain", "OK"); } void handleTZ() { if (server.hasArg("val")) { tzString = server.arg("val"); // Konfiguration sofort mit neuer Zeitzone aktualisieren configTime(tzString.c_str(), ntpServer.c_str()); Serial.println("Zeitzone geändert: " + tzString); } server.send(200, "text/plain", "OK"); } void handleColor(){ if(server.hasArg("c")){ lastColor=currentColor; currentColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant = true; updateDisplayNow(); } server.send(200,"text/plain","OK"); } void handleBGColor(){ if(server.hasArg("c")){ bgColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant = true; updateNeeded = true; } server.send(200,"text/plain","OK"); } void handleBrightness(){ brightness=server.arg("val").toInt(); forceInstant = true; updateNeeded = true; server.send(200,"text/plain","OK"); } void handleNight(){ autoNight=server.arg("val").toInt(); forceInstant = true; updateNeeded = true; server.send(200,"text/plain","OK"); } void handleBG(){ backgroundMode=server.arg("val").toInt(); forceInstant = true; updateNeeded = true; server.send(200,"text/plain","OK"); } void handleSave(){ saveSettings(); server.send(200,"text/plain","saved"); } void handleNightStart(){ int val = server.arg("val").toInt(); if(val >= 0 && val <= 23) { nightStart = val; forceInstant = true; updateNeeded = true; server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Ungueltiger Wert"); } } void handleNightEnd(){ int val = server.arg("val").toInt(); if(val >= 0 && val <= 23) { nightEnd = val; forceInstant = true; updateNeeded = true; server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Ungueltiger Wert"); } } void handleNightBrightness(){ nightBrightness = server.arg("val").toInt(); forceInstant = true; updateNeeded = true; server.send(200, "text/plain", "OK"); } void handleStatus() { time_t now = time(nullptr); struct tm *t = localtime(&now); char hexCol[10]; snprintf(hexCol, sizeof(hexCol), "#%06X", currentColor); char hexBg[10]; snprintf(hexBg, sizeof(hexBg), "#%06X",bgColor); String ntpStat = (now > 100000) ? "OK" : "SYNC..."; Serial.println(ntpStat); 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); // Hier KEIN Komma am Ende json += "}"; server.send(200, "application/json", json); } // ================= HTML ================= void handleRoot(){ String html = R"rawliteral(
--:--
Verbinde...
Nachtmodus (Auto) Zeitraum:
Von: Uhr Bis: Uhr
Nacht-Helligkeit:
Hintergrund Farbe
NTP Server: Zeitzone (POSIX TZ): TZ-Datenbank Hilfe
)rawliteral"; server.send(200, "text/html", html); } // ================= SETUP ================= void setup(){ Serial.begin(115200); WiFiManager wm; wm.autoConnect("Wortuhr"); configTime(tzString.c_str(), ntpServer.c_str()); for(int i=0;itm_min !=lastMinute){ lastMinute=t->tm_min; updateNeeded = true; } if(updateNeeded){ showTimeWords(t->tm_hour,t->tm_min); updateNeeded = false; } }