diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55dc28a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# SSH-Schluessel nie committen +.ssh/ diff --git a/SOFTWARE/arduino/arduino.ino b/SOFTWARE/arduino/arduino.ino new file mode 100644 index 0000000..73192dc --- /dev/null +++ b/SOFTWARE/arduino/arduino.ino @@ -0,0 +1,467 @@ +#include +#include +#include +#include +#include +#include +#include + +// ================= LED ================= +#define LED_PIN D4 +#define NUMPIXELS 110 +Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); + +// ================= SERVER & NTP ================= +ESP8266WebServer server(80); +const char DEFAULT_NTP[] = "de.pool.ntp.org"; +const char DEFAULT_TZ[] = "CET-1CEST,M3.5.0/02,M10.5.0/03"; +String ntpServer = DEFAULT_NTP; +String tzString = DEFAULT_TZ; + +// ================= SETTINGS ================= +#define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten + +struct Settings { + uint32_t magic; + int brightness; + int nightBrightness; + bool autoNight; + int nightStart; + int nightEnd; + uint32_t color; + uint32_t bgColor; + bool backgroundMode; + bool nightBackgroundDisabled; + char ntpServer[64]; + char tzString[64]; +} settings; + +// ================= VAR ================= +uint32_t currentColor = 0x00FFFF; +uint32_t bgColor = 0x000000; +bool backgroundMode = false; +bool nightBackgroundDisabled = 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]; // Soll-Farben (ohne Helligkeit/Gamma) +uint32_t currentBuffer[NUMPIXELS]; // zuletzt angezeigte Soll-Farben +int lastMinute = -1; +int lastH = -1; + +// ================= ZEIT ================= +bool timeSynced(){ + return time(nullptr) > 100000; +} + +// ================= EEPROM ================= +void saveSettings(){ + settings.magic = SETTINGS_MAGIC; + 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; + strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1); + settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; + strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1); + settings.tzString[sizeof(settings.tzString) - 1] = '\0'; + EEPROM.put(0, settings); + EEPROM.commit(); +} + +void loadSettings(){ + EEPROM.begin(512); + EEPROM.get(0, settings); + if(settings.magic == SETTINGS_MAGIC){ + brightness = constrain(settings.brightness, 0, 255); + nightBrightness = constrain(settings.nightBrightness, 0, 255); + autoNight = settings.autoNight; + nightStart = constrain(settings.nightStart, 0, 23); + nightEnd = constrain(settings.nightEnd, 0, 23); + currentColor = settings.color & 0xFFFFFF; + bgColor = settings.bgColor & 0xFFFFFF; + backgroundMode = settings.backgroundMode; + nightBackgroundDisabled = settings.nightBackgroundDisabled; + settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; + settings.tzString[sizeof(settings.tzString) - 1] = '\0'; + ntpServer = String(settings.ntpServer); + tzString = String(settings.tzString); + if(ntpServer.length() == 0) ntpServer = DEFAULT_NTP; + if(tzString.length() == 0) tzString = DEFAULT_TZ; + } + 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((uint8_t)(r1+(r2-r1)*t + 0.5f), + (uint8_t)(g1+(g2-g1)*t + 0.5f), + (uint8_t)(b1+(b2-b1)*t + 0.5f)); +} + +uint8_t gamma8(uint8_t x){ + float f = (float)x / 255.0f; + return (uint8_t)(f * f * 255.0f + 0.5f); +} + +// Skaliert einen Farbkanal mit der Helligkeit und korrigiert Gamma. +// Ein aktiver Kanal bleibt bei Helligkeit > 0 mindestens auf 1, damit er nicht erlischt. +uint8_t scaleChannel(uint8_t c, float factor){ + if(c == 0 || factor <= 0) return 0; + uint8_t v = gamma8((uint8_t)(c * factor)); + return v ? v : 1; +} + +uint32_t applyGammaBrightness(uint32_t c, float brightnessFactor){ + return pixels.Color(scaleChannel((c >> 16) & 255, brightnessFactor), + scaleChannel((c >> 8) & 255, brightnessFactor), + scaleChannel(c & 255, brightnessFactor)); +} + +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.0f; + for(int i=0;i= 25) h++; + const char* arr[] = {"Zwölf","Eins","Zwei","Drei","Vier","Fünf","Sechs","Sieben","Acht","Neun","Zehn","Elf"}; + String hName = arr[h%12]; + String t = "Es ist "; + switch(m5){ + case 0: t += (h%12 == 1) ? String("ein") : hName; t += " Uhr"; break; + case 5: t += "fünf nach " + hName; break; + case 10: t += "zehn nach " + hName; break; + case 15: t += "viertel nach " + hName; break; + case 20: t += "zwanzig nach " + hName; break; + case 25: t += "fünf vor halb " + hName; break; + case 30: t += "halb " + hName; break; + case 35: t += "fünf nach halb " + hName; break; + case 40: t += "zwanzig vor " + hName; break; + case 45: t += "viertel vor " + hName; break; + case 50: t += "zehn vor " + hName; break; + case 55: t += "fünf vor " + hName; break; + } + return t; +} + +void showTimeWords(int h, int m){ + time_t now = time(nullptr); + struct tm *t = localtime(&now); + + // Startpunkt des Fades ist die zuletzt angezeigte Soll-Farbe (ohne Helligkeit/Gamma) + 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 ================= +String jsonEscape(const String &s){ + String out; + for(unsigned int i = 0; i < s.length(); i++){ + char c = s[i]; + if(c == '"' || c == '\\'){ out += '\\'; out += c; } + else if((uint8_t)c < 0x20) out += ' '; + else out += c; + } + return out; +} + +void sendOK(){ server.send(200, "text/plain", "OK"); } + +// Liest den Parameter "val" als Zahl; sendet 400 und liefert false bei ungueltigem Wert. +bool argInRange(int lo, int hi, int &out){ + if(!server.hasArg("val")){ server.send(400, "text/plain", "Fehlender Wert"); return false; } + int v = server.arg("val").toInt(); + if(v < lo || v > hi){ server.send(400, "text/plain", "Ungueltiger Wert"); return false; } + out = v; + return true; +} + +void handleStatus(){ + time_t now = time(nullptr); + struct tm *t = localtime(&now); + char hexCol[10], hexBg[10]; + snprintf(hexCol, sizeof(hexCol), "#%06X", (unsigned int)currentColor); + snprintf(hexBg, sizeof(hexBg), "#%06X", (unsigned int)bgColor); + String ntpStat = timeSynced() ? "OK" : "SYNC..."; + String json = "{"; + json += "\"txt\":\"" + (timeSynced() ? jsonEscape(getTimeText(t->tm_hour, t->tm_min)) : String("Warte auf Zeit...")) + "\","; + json += "\"ip\":\"" + WiFi.localIP().toString() + "\","; + json += "\"ntp\":\"" + ntpStat + "\","; + json += "\"ntpServer\":\"" + jsonEscape(ntpServer) + "\","; + json += "\"tzString\":\"" + jsonEscape(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); +} + +static const char INDEX_HTML[] PROGMEM = R"rawliteral( + + +Wortuhr + +
Lade...
+
Verbinde...
+
+ + + + +
+
+ Hintergrund

+ +
+
+ Nachtmodus

+ Von: Uhr + Bis: Uhr

+ + + Hintergrund in Nacht aus +
+
+ + + + +
+
+ +)rawliteral"; + +void handleRoot(){ + server.send_P(200, "text/html", INDEX_HTML); +} + +void handleColor(){ + if(server.hasArg("c")){ + currentColor = strtoul(server.arg("c").c_str(), NULL, 16) & 0xFFFFFF; + forceInstant = true; updateNeeded = true; + } + sendOK(); +} +void handleBGColor(){ + if(server.hasArg("c")){ + bgColor = strtoul(server.arg("c").c_str(), NULL, 16) & 0xFFFFFF; + forceInstant = true; updateNeeded = true; + } + sendOK(); +} +void handleBrightness(){ int v; if(!argInRange(0,255,v)) return; brightness=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNight(){ int v; if(!argInRange(0,1,v)) return; autoNight=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightStart(){ int v; if(!argInRange(0,23,v)) return; nightStart=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightEnd(){ int v; if(!argInRange(0,23,v)) return; nightEnd=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightBrightness(){ int v; if(!argInRange(0,255,v)) return; nightBrightness=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightBgOff(){ int v; if(!argInRange(0,1,v)) return; nightBackgroundDisabled=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleBG(){ int v; if(!argInRange(0,1,v)) return; backgroundMode=v; forceInstant=true; updateNeeded=true; sendOK(); } + +void handleNTP(){ + String v = server.arg("val"); v.trim(); + if(v.length() == 0 || v.length() >= sizeof(settings.ntpServer)){ server.send(400, "text/plain", "Ungueltiger Wert"); return; } + ntpServer = v; + configTime(tzString.c_str(), ntpServer.c_str()); + sendOK(); +} +void handleTZ(){ + String v = server.arg("val"); v.trim(); + if(v.length() == 0 || v.length() >= sizeof(settings.tzString)){ server.send(400, "text/plain", "Ungueltiger Wert"); return; } + tzString = v; + configTime(tzString.c_str(), ntpServer.c_str()); + sendOK(); +} +void handleSave(){ saveSettings(); sendOK(); } + +// ================= SETUP & LOOP ================= +void setup(){ + Serial.begin(115200); + pixels.begin(); + pixels.clear(); + pixels.show(); + + loadSettings(); + + WiFiManager wm; + wm.autoConnect("Wortuhr"); + + 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(); +} + +void loop(){ + server.handleClient(); + + // Erst anzeigen, wenn die Zeit per NTP synchronisiert ist + if(!timeSynced()) return; + + time_t now = time(nullptr); + struct tm *t = localtime(&now); + + int currentM5 = (t->tm_min / 5) * 5; + if(currentM5 != lastMinute || t->tm_hour != lastH){ + lastMinute = currentM5; + lastH = t->tm_hour; + updateNeeded = true; + } + + if(updateNeeded){ + updateNeeded = false; + showTimeWords(t->tm_hour, t->tm_min); + } +} diff --git a/SOFTWARE/final_wortuhr.txt b/SOFTWARE/final_wortuhr.txt deleted file mode 100644 index ed6eb4c..0000000 --- a/SOFTWARE/final_wortuhr.txt +++ /dev/null @@ -1,686 +0,0 @@ -#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; - } -} \ No newline at end of file diff --git a/SOFTWARE/pio/.gitignore b/SOFTWARE/pio/.gitignore new file mode 100644 index 0000000..66fc7a3 --- /dev/null +++ b/SOFTWARE/pio/.gitignore @@ -0,0 +1 @@ +.pio/ diff --git a/SOFTWARE/pio/platformio.ini b/SOFTWARE/pio/platformio.ini new file mode 100644 index 0000000..6cf958a --- /dev/null +++ b/SOFTWARE/pio/platformio.ini @@ -0,0 +1,8 @@ +[env:d1_mini] +platform = espressif8266 +board = d1_mini +framework = arduino +monitor_speed = 115200 +lib_deps = + adafruit/Adafruit NeoPixel + tzapu/WiFiManager diff --git a/SOFTWARE/pio/src/main.cpp b/SOFTWARE/pio/src/main.cpp new file mode 100644 index 0000000..73192dc --- /dev/null +++ b/SOFTWARE/pio/src/main.cpp @@ -0,0 +1,467 @@ +#include +#include +#include +#include +#include +#include +#include + +// ================= LED ================= +#define LED_PIN D4 +#define NUMPIXELS 110 +Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); + +// ================= SERVER & NTP ================= +ESP8266WebServer server(80); +const char DEFAULT_NTP[] = "de.pool.ntp.org"; +const char DEFAULT_TZ[] = "CET-1CEST,M3.5.0/02,M10.5.0/03"; +String ntpServer = DEFAULT_NTP; +String tzString = DEFAULT_TZ; + +// ================= SETTINGS ================= +#define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten + +struct Settings { + uint32_t magic; + int brightness; + int nightBrightness; + bool autoNight; + int nightStart; + int nightEnd; + uint32_t color; + uint32_t bgColor; + bool backgroundMode; + bool nightBackgroundDisabled; + char ntpServer[64]; + char tzString[64]; +} settings; + +// ================= VAR ================= +uint32_t currentColor = 0x00FFFF; +uint32_t bgColor = 0x000000; +bool backgroundMode = false; +bool nightBackgroundDisabled = 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]; // Soll-Farben (ohne Helligkeit/Gamma) +uint32_t currentBuffer[NUMPIXELS]; // zuletzt angezeigte Soll-Farben +int lastMinute = -1; +int lastH = -1; + +// ================= ZEIT ================= +bool timeSynced(){ + return time(nullptr) > 100000; +} + +// ================= EEPROM ================= +void saveSettings(){ + settings.magic = SETTINGS_MAGIC; + 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; + strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1); + settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; + strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1); + settings.tzString[sizeof(settings.tzString) - 1] = '\0'; + EEPROM.put(0, settings); + EEPROM.commit(); +} + +void loadSettings(){ + EEPROM.begin(512); + EEPROM.get(0, settings); + if(settings.magic == SETTINGS_MAGIC){ + brightness = constrain(settings.brightness, 0, 255); + nightBrightness = constrain(settings.nightBrightness, 0, 255); + autoNight = settings.autoNight; + nightStart = constrain(settings.nightStart, 0, 23); + nightEnd = constrain(settings.nightEnd, 0, 23); + currentColor = settings.color & 0xFFFFFF; + bgColor = settings.bgColor & 0xFFFFFF; + backgroundMode = settings.backgroundMode; + nightBackgroundDisabled = settings.nightBackgroundDisabled; + settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; + settings.tzString[sizeof(settings.tzString) - 1] = '\0'; + ntpServer = String(settings.ntpServer); + tzString = String(settings.tzString); + if(ntpServer.length() == 0) ntpServer = DEFAULT_NTP; + if(tzString.length() == 0) tzString = DEFAULT_TZ; + } + 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((uint8_t)(r1+(r2-r1)*t + 0.5f), + (uint8_t)(g1+(g2-g1)*t + 0.5f), + (uint8_t)(b1+(b2-b1)*t + 0.5f)); +} + +uint8_t gamma8(uint8_t x){ + float f = (float)x / 255.0f; + return (uint8_t)(f * f * 255.0f + 0.5f); +} + +// Skaliert einen Farbkanal mit der Helligkeit und korrigiert Gamma. +// Ein aktiver Kanal bleibt bei Helligkeit > 0 mindestens auf 1, damit er nicht erlischt. +uint8_t scaleChannel(uint8_t c, float factor){ + if(c == 0 || factor <= 0) return 0; + uint8_t v = gamma8((uint8_t)(c * factor)); + return v ? v : 1; +} + +uint32_t applyGammaBrightness(uint32_t c, float brightnessFactor){ + return pixels.Color(scaleChannel((c >> 16) & 255, brightnessFactor), + scaleChannel((c >> 8) & 255, brightnessFactor), + scaleChannel(c & 255, brightnessFactor)); +} + +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.0f; + for(int i=0;i= 25) h++; + const char* arr[] = {"Zwölf","Eins","Zwei","Drei","Vier","Fünf","Sechs","Sieben","Acht","Neun","Zehn","Elf"}; + String hName = arr[h%12]; + String t = "Es ist "; + switch(m5){ + case 0: t += (h%12 == 1) ? String("ein") : hName; t += " Uhr"; break; + case 5: t += "fünf nach " + hName; break; + case 10: t += "zehn nach " + hName; break; + case 15: t += "viertel nach " + hName; break; + case 20: t += "zwanzig nach " + hName; break; + case 25: t += "fünf vor halb " + hName; break; + case 30: t += "halb " + hName; break; + case 35: t += "fünf nach halb " + hName; break; + case 40: t += "zwanzig vor " + hName; break; + case 45: t += "viertel vor " + hName; break; + case 50: t += "zehn vor " + hName; break; + case 55: t += "fünf vor " + hName; break; + } + return t; +} + +void showTimeWords(int h, int m){ + time_t now = time(nullptr); + struct tm *t = localtime(&now); + + // Startpunkt des Fades ist die zuletzt angezeigte Soll-Farbe (ohne Helligkeit/Gamma) + 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 ================= +String jsonEscape(const String &s){ + String out; + for(unsigned int i = 0; i < s.length(); i++){ + char c = s[i]; + if(c == '"' || c == '\\'){ out += '\\'; out += c; } + else if((uint8_t)c < 0x20) out += ' '; + else out += c; + } + return out; +} + +void sendOK(){ server.send(200, "text/plain", "OK"); } + +// Liest den Parameter "val" als Zahl; sendet 400 und liefert false bei ungueltigem Wert. +bool argInRange(int lo, int hi, int &out){ + if(!server.hasArg("val")){ server.send(400, "text/plain", "Fehlender Wert"); return false; } + int v = server.arg("val").toInt(); + if(v < lo || v > hi){ server.send(400, "text/plain", "Ungueltiger Wert"); return false; } + out = v; + return true; +} + +void handleStatus(){ + time_t now = time(nullptr); + struct tm *t = localtime(&now); + char hexCol[10], hexBg[10]; + snprintf(hexCol, sizeof(hexCol), "#%06X", (unsigned int)currentColor); + snprintf(hexBg, sizeof(hexBg), "#%06X", (unsigned int)bgColor); + String ntpStat = timeSynced() ? "OK" : "SYNC..."; + String json = "{"; + json += "\"txt\":\"" + (timeSynced() ? jsonEscape(getTimeText(t->tm_hour, t->tm_min)) : String("Warte auf Zeit...")) + "\","; + json += "\"ip\":\"" + WiFi.localIP().toString() + "\","; + json += "\"ntp\":\"" + ntpStat + "\","; + json += "\"ntpServer\":\"" + jsonEscape(ntpServer) + "\","; + json += "\"tzString\":\"" + jsonEscape(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); +} + +static const char INDEX_HTML[] PROGMEM = R"rawliteral( + + +Wortuhr + +
Lade...
+
Verbinde...
+
+ + + + +
+
+ Hintergrund

+ +
+
+ Nachtmodus

+ Von: Uhr + Bis: Uhr

+ + + Hintergrund in Nacht aus +
+
+ + + + +
+
+ +)rawliteral"; + +void handleRoot(){ + server.send_P(200, "text/html", INDEX_HTML); +} + +void handleColor(){ + if(server.hasArg("c")){ + currentColor = strtoul(server.arg("c").c_str(), NULL, 16) & 0xFFFFFF; + forceInstant = true; updateNeeded = true; + } + sendOK(); +} +void handleBGColor(){ + if(server.hasArg("c")){ + bgColor = strtoul(server.arg("c").c_str(), NULL, 16) & 0xFFFFFF; + forceInstant = true; updateNeeded = true; + } + sendOK(); +} +void handleBrightness(){ int v; if(!argInRange(0,255,v)) return; brightness=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNight(){ int v; if(!argInRange(0,1,v)) return; autoNight=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightStart(){ int v; if(!argInRange(0,23,v)) return; nightStart=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightEnd(){ int v; if(!argInRange(0,23,v)) return; nightEnd=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightBrightness(){ int v; if(!argInRange(0,255,v)) return; nightBrightness=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleNightBgOff(){ int v; if(!argInRange(0,1,v)) return; nightBackgroundDisabled=v; forceInstant=true; updateNeeded=true; sendOK(); } +void handleBG(){ int v; if(!argInRange(0,1,v)) return; backgroundMode=v; forceInstant=true; updateNeeded=true; sendOK(); } + +void handleNTP(){ + String v = server.arg("val"); v.trim(); + if(v.length() == 0 || v.length() >= sizeof(settings.ntpServer)){ server.send(400, "text/plain", "Ungueltiger Wert"); return; } + ntpServer = v; + configTime(tzString.c_str(), ntpServer.c_str()); + sendOK(); +} +void handleTZ(){ + String v = server.arg("val"); v.trim(); + if(v.length() == 0 || v.length() >= sizeof(settings.tzString)){ server.send(400, "text/plain", "Ungueltiger Wert"); return; } + tzString = v; + configTime(tzString.c_str(), ntpServer.c_str()); + sendOK(); +} +void handleSave(){ saveSettings(); sendOK(); } + +// ================= SETUP & LOOP ================= +void setup(){ + Serial.begin(115200); + pixels.begin(); + pixels.clear(); + pixels.show(); + + loadSettings(); + + WiFiManager wm; + wm.autoConnect("Wortuhr"); + + 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(); +} + +void loop(){ + server.handleClient(); + + // Erst anzeigen, wenn die Zeit per NTP synchronisiert ist + if(!timeSynced()) return; + + time_t now = time(nullptr); + struct tm *t = localtime(&now); + + int currentM5 = (t->tm_min / 5) * 5; + if(currentM5 != lastMinute || t->tm_hour != lastH){ + lastMinute = currentM5; + lastH = t->tm_hour; + updateNeeded = true; + } + + if(updateNeeded){ + updateNeeded = false; + showTimeWords(t->tm_hour, t->tm_min); + } +} diff --git a/SOFTWARE/wordclock_finalversion.ino b/SOFTWARE/wordclock_finalversion.ino deleted file mode 100644 index de2dd0e..0000000 --- a/SOFTWARE/wordclock_finalversion.ino +++ /dev/null @@ -1,378 +0,0 @@ -#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; - } -} \ No newline at end of file