13 Commits
Author SHA1 Message Date
lukarioandClaude Sonnet 5.5 f762b97f6f Pegelwandler gegen den Uhrzeigersinn gedreht, U3.2 auf Hoehe von U1.1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:07:32 +02:00
lukarioandClaude Sonnet 5.5 c6c41e1f5d Widerstaende umsortiert: R1, R4, R2, R3 von oben nach unten
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:53:25 +02:00
lukarioandClaude Sonnet 5.5 258f68185e F1, D1 nach rechts, C1 links neben D1, Widerstaende nach links verschoben
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:49:15 +02:00
lukarioandClaude Sonnet 5.5 e74580fa11 Montagelöcher entfernt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:11:26 +02:00
lukarioandClaude Sonnet 5.5 e4febb1035 J2 und J3 in einer Linie zwischen J1 und ESP verschoben (J2 links), Titeltext angepasst
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:08:34 +02:00
lukarioandClaude Sonnet 5.5 a11285d332 J1.1 mit H1 getauscht und gedreht (+ zeigt zum ESP), Titeltext verschoben
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:04:47 +02:00
lukarioandClaude Sonnet 5.5 f815962d7a J1.1 als 2-poliger Schraubklemmen-Anschluss (5,08 mm) statt Stiftleiste
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:01:34 +02:00
lukarioandClaude Sonnet 5.5 7cc7a1249f J1.1-J1.4 durch einen 2-poligen Stecker J1.1 (+ 5V / - GND) ersetzt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 18:50:39 +02:00
lukarioandClaude Sonnet 5.5 dd38e291de Pegelwandler und DS3231 getauscht: LV-Seite zur ESP, U2.1 zum Pegelwandler
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 18:28:30 +02:00
lukarioandClaude Sonnet 5.5 f579d1e1c8 NodeMCU-Beschriftung oberhalb der Stiftleiste, nur benoetigte Pins auf F.Silkscreen, Rest auf B.Silkscreen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:25:38 +02:00
lukarioandClaude Sonnet 5.5 d6b6cc9a2b D1-mini-Buchsen um 1,27 mm versetzt, Beschriftung der verbundenen Pins auf F.Silkscreen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:23:13 +02:00
lukarioandClaude Sonnet 5.5 51f5ed1744 D1-mini-Buchsenleisten (U4.1/U4.2) im Schaltplan und auf der Rueckseite ergaenzt, ESP-Beschriftung gedreht
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:22:54 +02:00
lukarioandClaude Sonnet 5.5 a4835b057a NodeMCU auf dem Board um 180 Grad gedreht (USB nach aussen), Beschriftungen angepasst
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 20:52:52 +02:00
6 changed files with 2563 additions and 3766 deletions

No files matched your search

-4
View File
@@ -35,9 +35,6 @@ Am Anfang den ESP anlöten:
## Flashen ## Flashen
**Board und RTC wählen:** PlatformIO: Umgebung `<board>_<rtc>` mit Board `d1_mini` oder `nodemcu` und RTC `ds3231`, `ds1307` oder `keine` (z. B. `pio run -e nodemcu_ds3231 -t upload`). Arduino IDE: Board unter Werkzeuge wählen und oben in `arduino.ino` `RTC_MODUL_DS3231`, `RTC_MODUL_DS1307` oder `RTC_MODUL_KEINE` definieren (Standard: DS3231). Die RTC hängt an D2 (SDA) / D1 (SCL); der DS1307 braucht 5 V.
1. Dieses Repo clonen (`git clone https://github.com/c3e/wordclock.git`) 1. Dieses Repo clonen (`git clone https://github.com/c3e/wordclock.git`)
2. Die Arduino IDE installieren (https://www.arduino.cc/en/Main/Software) 2. Die Arduino IDE installieren (https://www.arduino.cc/en/Main/Software)
3. Die Datei `wordclock/wordclock.ino` in der Arduino IDE öffnen 3. Die Datei `wordclock/wordclock.ino` in der Arduino IDE öffnen
@@ -48,7 +45,6 @@ Am Anfang den ESP anlöten:
8. Folgende installieren: 8. Folgende installieren:
* FastLED * FastLED
* WiFiManager * WiFiManager
* RTClib (Adafruit)
9. Folgende Zip-Dateien herunterladen: 9. Folgende Zip-Dateien herunterladen:
* https://github.com/PaulStoffregen/Time/archive/master.zip * https://github.com/PaulStoffregen/Time/archive/master.zip
* https://github.com/JChristensen/Timezone/archive/master.zip * https://github.com/JChristensen/Timezone/archive/master.zip
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+29 -203
View File
@@ -5,40 +5,10 @@
#include <EEPROM.h> #include <EEPROM.h>
#include <time.h> #include <time.h>
#include <WiFiManager.h> #include <WiFiManager.h>
#include <Wire.h>
#include <sys/time.h>
// ================= RTC =================
// RTC-Modul waehlen (vor dem Kompilieren): RTC_MODUL_DS3231, RTC_MODUL_DS1307 oder RTC_MODUL_KEINE.
// PlatformIO: ueber die Umgebung (siehe platformio.ini). Arduino IDE: hier eintragen.
// Das Board (D1 mini / NodeMCU) waehlt man in der IDE bzw. ebenfalls ueber die Umgebung;
// die Pinnamen D1/D2/D4 sind bei beiden identisch.
#if !defined(RTC_MODUL_DS3231) && !defined(RTC_MODUL_DS1307) && !defined(RTC_MODUL_KEINE)
#define RTC_MODUL_DS3231
#endif
#if defined(RTC_MODUL_DS3231) || defined(RTC_MODUL_DS1307)
#include <RTClib.h>
#define RTC_VORHANDEN
#endif
#if defined(RTC_MODUL_DS3231)
RTC_DS3231 rtc;
#elif defined(RTC_MODUL_DS1307)
RTC_DS1307 rtc;
#endif
#define RTC_SDA D2
#define RTC_SCL D1
bool rtcOk = false;
bool rtcBatteryEmpty = false; // RTC hat beim Start die Zeit verloren
volatile bool ntpJustSynced = false;
void onTimeSet(bool fromSntp){ if(fromSntp) ntpJustSynced = true; }
// ================= LED ================= // ================= LED =================
#define LED_PIN D4 #define LED_PIN D4
// Neue Aufteilung (cad/openscad/gemeinsam.scad): je Wort ein LED-Streifen mit einer #define NUMPIXELS 110
// LED pro Buchstabe, Zickzack durch die Zeilen. Zeile 0 laeuft links->rechts, Zeile 1
// rechts->links usw. Die Fuellbuchstaben gibt es nicht, nur die Woerter (inkl. NTP,
// API, RTC, ESP). Minutenpunkte sind abgeschaltet.
#define NUMPIXELS 104
Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
// ================= SERVER & NTP ================= // ================= SERVER & NTP =================
@@ -49,7 +19,7 @@ String ntpServer = DEFAULT_NTP;
String tzString = DEFAULT_TZ; String tzString = DEFAULT_TZ;
// ================= SETTINGS ================= // ================= SETTINGS =================
#define SETTINGS_MAGIC 0x57554853UL // erkennt gueltige EEPROM-Daten #define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten
struct Settings { struct Settings {
uint32_t magic; uint32_t magic;
@@ -62,8 +32,6 @@ struct Settings {
uint32_t bgColor; uint32_t bgColor;
bool backgroundMode; bool backgroundMode;
bool nightBackgroundDisabled; bool nightBackgroundDisabled;
bool statusLeds;
int ntpShowSec;
char ntpServer[64]; char ntpServer[64];
char tzString[64]; char tzString[64];
} settings; } settings;
@@ -73,8 +41,6 @@ uint32_t currentColor = 0x00FFFF;
uint32_t bgColor = 0x000000; uint32_t bgColor = 0x000000;
bool backgroundMode = false; bool backgroundMode = false;
bool nightBackgroundDisabled = false; bool nightBackgroundDisabled = false;
bool statusLeds = true; // Statuswoerter NTP/RTC/ESP anzeigen
int ntpShowSec = 10; // Mindestdauer der gruenen NTP-Anzeige
bool forceInstant = false; bool forceInstant = false;
bool updateNeeded = true; bool updateNeeded = true;
int brightness = 120; int brightness = 120;
@@ -104,8 +70,6 @@ void saveSettings(){
settings.bgColor = bgColor; settings.bgColor = bgColor;
settings.backgroundMode = backgroundMode; settings.backgroundMode = backgroundMode;
settings.nightBackgroundDisabled = nightBackgroundDisabled; settings.nightBackgroundDisabled = nightBackgroundDisabled;
settings.statusLeds = statusLeds;
settings.ntpShowSec = ntpShowSec;
strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1); strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1);
settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0';
strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1); strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1);
@@ -127,8 +91,6 @@ void loadSettings(){
bgColor = settings.bgColor & 0xFFFFFF; bgColor = settings.bgColor & 0xFFFFFF;
backgroundMode = settings.backgroundMode; backgroundMode = settings.backgroundMode;
nightBackgroundDisabled = settings.nightBackgroundDisabled; nightBackgroundDisabled = settings.nightBackgroundDisabled;
statusLeds = settings.statusLeds;
ntpShowSec = constrain(settings.ntpShowSec, 1, 60);
settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0';
settings.tzString[sizeof(settings.tzString) - 1] = '\0'; settings.tzString[sizeof(settings.tzString) - 1] = '\0';
ntpServer = String(settings.ntpServer); ntpServer = String(settings.ntpServer);
@@ -139,109 +101,6 @@ void loadSettings(){
configTime(tzString.c_str(), ntpServer.c_str()); configTime(tzString.c_str(), ntpServer.c_str());
} }
// Zeit aus der RTC uebernehmen. Die RTC laeuft in UTC.
#define RTC_INTERVAL_MS 3600000UL // ohne NTP: RTC stuendlich neu einlesen
#define NTP_GUELTIG_MS 7200000UL // NTP-Sync gilt 2 h als aktuell
unsigned long lastRtcReadMs = 0;
unsigned long lastNtpMs = 0; // 0 = noch kein NTP-Sync seit Start
bool ntpShowing = false; // NTP-Wort leuchtet gerade
unsigned long ntpShowUntil = 0;
bool ntpAktuell(){
return lastNtpMs != 0 && millis() - lastNtpMs < NTP_GUELTIG_MS;
}
// Liest die RTC; liefert false, wenn sie keine gueltige Zeit hat (Batterie leer).
bool readRtcToSystem(){
#ifdef RTC_VORHANDEN
lastRtcReadMs = millis();
#if defined(RTC_MODUL_DS3231)
bool valid = !rtc.lostPower();
#else
bool valid = rtc.isrunning();
#endif
DateTime n = rtc.now();
if(valid && n.year() >= 2024){
struct timeval tv = { (time_t)n.unixtime(), 0 };
settimeofday(&tv, nullptr);
ntpJustSynced = false; // der eigene Aufruf ist keine NTP-Zeit
return true;
}
#endif
return false;
}
void initRtc(){
#ifdef RTC_VORHANDEN
Wire.begin(RTC_SDA, RTC_SCL);
if(!rtc.begin()) return;
rtcOk = true;
// Nach dem Nachstellen per NTP ist der Batteriestatus nicht mehr erkennbar,
// daher wird er nur beim Start bestimmt.
rtcBatteryEmpty = !readRtcToSystem();
#endif
}
// Nach jeder NTP-Synchronisation die RTC nachstellen; ohne aktuellen NTP-Sync
// stuendlich die Systemzeit aus der RTC auffrischen.
void updateTimeSources(){
if(ntpJustSynced){
ntpJustSynced = false;
if(timeSynced()){
lastNtpMs = millis() ? millis() : 1;
ntpShowing = true;
ntpShowUntil = millis() + (unsigned long)ntpShowSec * 1000UL;
#ifdef RTC_VORHANDEN
if(rtcOk) rtc.adjust(DateTime((uint32_t)time(nullptr)));
#endif
}
}
if(ntpShowing && (long)(millis() - ntpShowUntil) >= 0) ntpShowing = false;
#ifdef RTC_VORHANDEN
if(rtcOk && !rtcBatteryEmpty && !ntpAktuell() && millis() - lastRtcReadMs >= RTC_INTERVAL_MS)
readRtcToSystem();
#endif
}
// ================= STATUS-WOERTER =================
// NTP (gruen): waehrend/kurz nach dem NTP-Abgleich
// RTC (gelb): RTC vorhanden, aber kein aktueller NTP-Abgleich; bei leerer Batterie
// blinkt das Wort im 10-s-Takt
// ESP (rot): weder gueltige RTC noch NTP
#define STATUS_GRUEN 0x00FF00
#define STATUS_GELB 0xFFFF00
#define STATUS_ROT 0xFF0000
struct StatusFarben { uint32_t ntp, rtc, esp; };
StatusFarben statusAktuell = {0, 0, 0};
StatusFarben berechneStatus(){
StatusFarben st = {0, 0, 0};
if(!statusLeds) return st;
bool ntpOk = ntpAktuell();
bool rtcGut = rtcOk && !rtcBatteryEmpty;
if(ntpShowing) st.ntp = STATUS_GRUEN;
if(rtcOk){
if(rtcBatteryEmpty) st.rtc = ((millis() / 10000UL) & 1) ? 0 : STATUS_GELB;
else if(!ntpOk) st.rtc = STATUS_GELB;
}
if(!rtcGut && !ntpOk) st.esp = STATUS_ROT;
return st;
}
// Aenderung der Statusanzeige erkennen (loest ein Neuzeichnen aus)
bool statusGeaendert(){
StatusFarben st = berechneStatus();
bool changed = st.ntp != statusAktuell.ntp || st.rtc != statusAktuell.rtc || st.esp != statusAktuell.esp;
statusAktuell = st;
return changed;
}
void applyStatus(){
for(int i=34;i<=36;i++) if(statusAktuell.ntp) targetBuffer[i] = statusAktuell.ntp;
for(int i=56;i<=58;i++) if(statusAktuell.rtc) targetBuffer[i] = statusAktuell.rtc;
for(int i=67;i<=69;i++) if(statusAktuell.esp) targetBuffer[i] = statusAktuell.esp;
}
// ================= HELFER ================= // ================= HELFER =================
bool isNightTime(struct tm *t){ bool isNightTime(struct tm *t){
if(!autoNight) return false; if(!autoNight) return false;
@@ -322,34 +181,29 @@ void fadeToTargetSmooth(int steps = 30, int delayMs = 15){
} }
// ================= WORTUHR LOGIK ================= // ================= WORTUHR LOGIK =================
// LED-Indizes in Kettenreihenfolge (Zeilen im Zickzack, Woerter je Zeile in Laufrichtung) void word_ES(){ setRange(0,1); }
void word_ES(){ setRange(0,1); } // Zeile 0 (links->rechts) void word_IST(){ setRange(3,5); }
void word_IST(){ setRange(2,4); } void word_FUENF_M(){ setRange(7,10); }
void word_FUENF_M(){ setRange(5,8); } void word_ZEHN_M(){ setRange(18,21); }
void word_ZEHN_M(){ setRange(9,12); } // Zeile 1 (rechts->links) void word_ZWANZIG(){ setRange(11,17); }
void word_ZWANZIG(){ setRange(13,19); } void word_VIERTEL(){ setRange(22,28); }
void word_VIERTEL(){ setRange(20,26); } // Zeile 2 void word_VOR(){ setRange(30,32); }
void word_VOR(){ setRange(27,29); } void word_NACH(){ setRange(40,43); }
void word_NACH(){ setRange(30,33); } // Zeile 3 (rechts->links) void word_HALB(){ setRange(33,36); }
// NTP 34-36 void word_EINS(){ setRange(62,65); }
void word_HALB(){ setRange(37,40); } void word_EIN(){ setRange(62,64); }
void word_NEUN(){ setRange(41,44); } // Zeile 4 void word_ZWEI(){ setRange(55,58); }
// API 45-47 void word_DREI(){ setRange(66,69); }
void word_FUENF_H(){ setRange(48,51); } void word_VIER(){ setRange(73,76); }
void word_EINS(){ setRange(52,55); } // Zeile 5 (rechts->links): S, EIN, RTC, ZWEI void word_FUENF_H(){ setRange(51,54); }
void word_EIN(){ setRange(53,55); } void word_SECHS(){ setRange(83,87); }
// RTC 56-58 void word_SIEBEN(){ setRange(88,93); }
void word_ZWEI(){ setRange(59,62); } void word_ACHT(){ setRange(77,80); }
void word_DREI(){ setRange(63,66); } // Zeile 6 void word_NEUN(){ setRange(44,47); }
// ESP 67-69 void word_ZEHN_H(){ setRange(106,109); }
void word_VIER(){ setRange(70,73); } void word_ELF(){ setRange(103,105); }
void word_SECHS(){ setRange(74,78); } // Zeile 7 (rechts->links) void word_ZWOELF(){ setRange(94,98); }
void word_ACHT(){ setRange(79,82); } void word_UHR(){ setRange(99,101); }
void word_SIEBEN(){ setRange(83,88); } // Zeile 8
void word_ZWOELF(){ setRange(89,93); }
void word_ZEHN_H(){ setRange(94,97); } // Zeile 9 (rechts->links)
void word_ELF(){ setRange(98,100); }
void word_UHR(){ setRange(101,103); }
String getTimeText(int h, int m){ String getTimeText(int h, int m){
int m5 = (m/5)*5; int m5 = (m/5)*5;
@@ -411,21 +265,10 @@ void showTimeWords(int h, int m){
else if(hh == 7) word_SIEBEN(); else if(hh == 8) word_ACHT(); else if(hh == 9) word_NEUN(); 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(); else if(hh == 10) word_ZEHN_H(); else if(hh == 11) word_ELF(); else word_ZWOELF();
applyStatus();
if(forceInstant){ showInstant(); forceInstant = false; } if(forceInstant){ showInstant(); forceInstant = false; }
else { fadeToTargetSmooth(20, 10); } else { fadeToTargetSmooth(20, 10); }
} }
// Solange noch keine Zeit bekannt ist, nur Hintergrund und Statuswoerter zeigen
void showStatusOnly(){
for(int i=0;i<NUMPIXELS;i++) currentBuffer[i] = targetBuffer[i];
uint32_t activeBg = backgroundMode ? bgColor : 0;
for(int i=0;i<NUMPIXELS;i++) targetBuffer[i] = activeBg;
applyStatus();
showInstant();
}
// ================= WEB HANDLERS ================= // ================= WEB HANDLERS =================
String jsonEscape(const String &s){ String jsonEscape(const String &s){
String out; String out;
@@ -470,9 +313,7 @@ void handleStatus(){
json += "\"nightStart\":" + String(nightStart) + ","; json += "\"nightStart\":" + String(nightStart) + ",";
json += "\"nightEnd\":" + String(nightEnd) + ","; json += "\"nightEnd\":" + String(nightEnd) + ",";
json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ","; json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ",";
json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0) + ","; json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0);
json += "\"statusLeds\":" + String(statusLeds ? 1 : 0) + ",";
json += "\"ntpShowSec\":" + String(ntpShowSec);
json += "}"; json += "}";
server.send(200, "application/json", json); server.send(200, "application/json", json);
} }
@@ -510,10 +351,6 @@ static const char INDEX_HTML[] PROGMEM = R"rawliteral(
<input type='range' id='nbr' min='0' max='255' oninput="f('/nightBrightness?val='+this.value)"> <input type='range' id='nbr' min='0' max='255' oninput="f('/nightBrightness?val='+this.value)">
<input type='checkbox' id='nightBgOff' onchange="f('/nightBgOff?val='+(this.checked?1:0))"> Hintergrund in Nacht aus <input type='checkbox' id='nightBgOff' onchange="f('/nightBgOff?val='+(this.checked?1:0))"> Hintergrund in Nacht aus
</div> </div>
<div class='card'>
<input type='checkbox' id='sl' onchange="f('/statusLeds?val='+(this.checked?1:0))"> <b>Status-Anzeige</b> (NTP gr&uuml;n, RTC gelb, ESP rot)<br><br>
NTP-Anzeige mind. <input type='number' id='nsec' min='1' max='60' style='width:50px' oninput="if(this.value!=='')f('/ntpShowSec?val='+this.value)"> Sekunden
</div>
<div class='card'> <div class='card'>
<label>NTP Server:</label> <label>NTP Server:</label>
<input type='text' id='ntpS' onchange="f('/setNTP?val='+encodeURIComponent(this.value))"> <input type='text' id='ntpS' onchange="f('/setNTP?val='+encodeURIComponent(this.value))">
@@ -530,7 +367,7 @@ static const char INDEX_HTML[] PROGMEM = R"rawliteral(
const s=(i,v,c=false)=>{const e=document.getElementById(i);if(e&&document.activeElement!==e){if(c)e.checked=(v==1);else e.value=v;}}; const s=(i,v,c=false)=>{const e=document.getElementById(i);if(e&&document.activeElement!==e){if(c)e.checked=(v==1);else e.value=v;}};
s('col',d.color);s('br',d.brightness);s('night',d.night,true);s('nS',d.nightStart);s('nE',d.nightEnd); s('col',d.color);s('br',d.brightness);s('night',d.night,true);s('nS',d.nightStart);s('nE',d.nightEnd);
s('nbr',d.nightBrightness);s('bg',d.bg,true);s('bgcol',d.bgColor);s('ntpS',d.ntpServer);s('tzS',d.tzString); s('nbr',d.nightBrightness);s('bg',d.bg,true);s('bgcol',d.bgColor);s('ntpS',d.ntpServer);s('tzS',d.tzString);
s('nightBgOff',d.nightBgOff,true);s('sl',d.statusLeds,true);s('nsec',d.ntpShowSec); s('nightBgOff',d.nightBgOff,true);
}).catch(()=>{document.getElementById('status').innerText='Verbindung unterbrochen...';})} }).catch(()=>{document.getElementById('status').innerText='Verbindung unterbrochen...';})}
setInterval(u, 2500); u(); setInterval(u, 2500); u();
</script></body></html> </script></body></html>
@@ -560,8 +397,6 @@ void handleNightStart(){ int v; if(!argInRange(0,23,v)) return; nightStart=v; fo
void handleNightEnd(){ int v; if(!argInRange(0,23,v)) return; nightEnd=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 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 handleNightBgOff(){ int v; if(!argInRange(0,1,v)) return; nightBackgroundDisabled=v; forceInstant=true; updateNeeded=true; sendOK(); }
void handleStatusLeds(){ int v; if(!argInRange(0,1,v)) return; statusLeds=v; forceInstant=true; updateNeeded=true; sendOK(); }
void handleNtpShowSec(){ int v; if(!argInRange(1,60,v)) return; ntpShowSec=v; sendOK(); }
void handleBG(){ int v; if(!argInRange(0,1,v)) return; backgroundMode=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(){ void handleNTP(){
@@ -587,9 +422,7 @@ void setup(){
pixels.clear(); pixels.clear();
pixels.show(); pixels.show();
settimeofday_cb(onTimeSet);
loadSettings(); loadSettings();
initRtc();
WiFiManager wm; WiFiManager wm;
wm.autoConnect("Wortuhr"); wm.autoConnect("Wortuhr");
@@ -605,8 +438,6 @@ void setup(){
server.on("/nightBrightness", handleNightBrightness); server.on("/nightBrightness", handleNightBrightness);
server.on("/nightBgOff", handleNightBgOff); server.on("/nightBgOff", handleNightBgOff);
server.on("/bg", handleBG); server.on("/bg", handleBG);
server.on("/statusLeds", handleStatusLeds);
server.on("/ntpShowSec", handleNtpShowSec);
server.on("/setNTP", handleNTP); server.on("/setNTP", handleNTP);
server.on("/setTZ", handleTZ); server.on("/setTZ", handleTZ);
server.on("/save", handleSave); server.on("/save", handleSave);
@@ -615,14 +446,9 @@ void setup(){
void loop(){ void loop(){
server.handleClient(); server.handleClient();
updateTimeSources();
if(statusGeaendert()){ forceInstant = true; updateNeeded = true; }
// Ohne bekannte Zeit nur die Statuswoerter anzeigen // Erst anzeigen, wenn die Zeit per NTP synchronisiert ist
if(!timeSynced()){ if(!timeSynced()) return;
if(updateNeeded){ updateNeeded = false; forceInstant = false; showStatusOnly(); }
return;
}
time_t now = time(nullptr); time_t now = time(nullptr);
struct tm *t = localtime(&now); struct tm *t = localtime(&now);
+2 -34
View File
@@ -1,40 +1,8 @@
; Umgebung = Board + RTC-Modul waehlen, z.B.: pio run -e nodemcu_ds3231 -t upload [env:d1_mini]
; Board: d1_mini | nodemcu RTC: ds3231 | ds1307 | keine
[common]
platform = espressif8266 platform = espressif8266
board = d1_mini
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
lib_deps = lib_deps =
adafruit/Adafruit NeoPixel adafruit/Adafruit NeoPixel
tzapu/WiFiManager tzapu/WiFiManager
adafruit/RTClib
[board_d1_mini]
board = d1_mini
[board_nodemcu]
board = nodemcuv2
[env:d1_mini_ds3231]
extends = common, board_d1_mini
build_flags = -DRTC_MODUL_DS3231
[env:d1_mini_ds1307]
extends = common, board_d1_mini
build_flags = -DRTC_MODUL_DS1307
[env:d1_mini_keine]
extends = common, board_d1_mini
build_flags = -DRTC_MODUL_KEINE
[env:nodemcu_ds3231]
extends = common, board_nodemcu
build_flags = -DRTC_MODUL_DS3231
[env:nodemcu_ds1307]
extends = common, board_nodemcu
build_flags = -DRTC_MODUL_DS1307
[env:nodemcu_keine]
extends = common, board_nodemcu
build_flags = -DRTC_MODUL_KEINE
+29 -203
View File
@@ -5,40 +5,10 @@
#include <EEPROM.h> #include <EEPROM.h>
#include <time.h> #include <time.h>
#include <WiFiManager.h> #include <WiFiManager.h>
#include <Wire.h>
#include <sys/time.h>
// ================= RTC =================
// RTC-Modul waehlen (vor dem Kompilieren): RTC_MODUL_DS3231, RTC_MODUL_DS1307 oder RTC_MODUL_KEINE.
// PlatformIO: ueber die Umgebung (siehe platformio.ini). Arduino IDE: hier eintragen.
// Das Board (D1 mini / NodeMCU) waehlt man in der IDE bzw. ebenfalls ueber die Umgebung;
// die Pinnamen D1/D2/D4 sind bei beiden identisch.
#if !defined(RTC_MODUL_DS3231) && !defined(RTC_MODUL_DS1307) && !defined(RTC_MODUL_KEINE)
#define RTC_MODUL_DS3231
#endif
#if defined(RTC_MODUL_DS3231) || defined(RTC_MODUL_DS1307)
#include <RTClib.h>
#define RTC_VORHANDEN
#endif
#if defined(RTC_MODUL_DS3231)
RTC_DS3231 rtc;
#elif defined(RTC_MODUL_DS1307)
RTC_DS1307 rtc;
#endif
#define RTC_SDA D2
#define RTC_SCL D1
bool rtcOk = false;
bool rtcBatteryEmpty = false; // RTC hat beim Start die Zeit verloren
volatile bool ntpJustSynced = false;
void onTimeSet(bool fromSntp){ if(fromSntp) ntpJustSynced = true; }
// ================= LED ================= // ================= LED =================
#define LED_PIN D4 #define LED_PIN D4
// Neue Aufteilung (cad/openscad/gemeinsam.scad): je Wort ein LED-Streifen mit einer #define NUMPIXELS 110
// LED pro Buchstabe, Zickzack durch die Zeilen. Zeile 0 laeuft links->rechts, Zeile 1
// rechts->links usw. Die Fuellbuchstaben gibt es nicht, nur die Woerter (inkl. NTP,
// API, RTC, ESP). Minutenpunkte sind abgeschaltet.
#define NUMPIXELS 104
Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
// ================= SERVER & NTP ================= // ================= SERVER & NTP =================
@@ -49,7 +19,7 @@ String ntpServer = DEFAULT_NTP;
String tzString = DEFAULT_TZ; String tzString = DEFAULT_TZ;
// ================= SETTINGS ================= // ================= SETTINGS =================
#define SETTINGS_MAGIC 0x57554853UL // erkennt gueltige EEPROM-Daten #define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten
struct Settings { struct Settings {
uint32_t magic; uint32_t magic;
@@ -62,8 +32,6 @@ struct Settings {
uint32_t bgColor; uint32_t bgColor;
bool backgroundMode; bool backgroundMode;
bool nightBackgroundDisabled; bool nightBackgroundDisabled;
bool statusLeds;
int ntpShowSec;
char ntpServer[64]; char ntpServer[64];
char tzString[64]; char tzString[64];
} settings; } settings;
@@ -73,8 +41,6 @@ uint32_t currentColor = 0x00FFFF;
uint32_t bgColor = 0x000000; uint32_t bgColor = 0x000000;
bool backgroundMode = false; bool backgroundMode = false;
bool nightBackgroundDisabled = false; bool nightBackgroundDisabled = false;
bool statusLeds = true; // Statuswoerter NTP/RTC/ESP anzeigen
int ntpShowSec = 10; // Mindestdauer der gruenen NTP-Anzeige
bool forceInstant = false; bool forceInstant = false;
bool updateNeeded = true; bool updateNeeded = true;
int brightness = 120; int brightness = 120;
@@ -104,8 +70,6 @@ void saveSettings(){
settings.bgColor = bgColor; settings.bgColor = bgColor;
settings.backgroundMode = backgroundMode; settings.backgroundMode = backgroundMode;
settings.nightBackgroundDisabled = nightBackgroundDisabled; settings.nightBackgroundDisabled = nightBackgroundDisabled;
settings.statusLeds = statusLeds;
settings.ntpShowSec = ntpShowSec;
strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1); strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer) - 1);
settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0';
strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1); strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString) - 1);
@@ -127,8 +91,6 @@ void loadSettings(){
bgColor = settings.bgColor & 0xFFFFFF; bgColor = settings.bgColor & 0xFFFFFF;
backgroundMode = settings.backgroundMode; backgroundMode = settings.backgroundMode;
nightBackgroundDisabled = settings.nightBackgroundDisabled; nightBackgroundDisabled = settings.nightBackgroundDisabled;
statusLeds = settings.statusLeds;
ntpShowSec = constrain(settings.ntpShowSec, 1, 60);
settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0'; settings.ntpServer[sizeof(settings.ntpServer) - 1] = '\0';
settings.tzString[sizeof(settings.tzString) - 1] = '\0'; settings.tzString[sizeof(settings.tzString) - 1] = '\0';
ntpServer = String(settings.ntpServer); ntpServer = String(settings.ntpServer);
@@ -139,109 +101,6 @@ void loadSettings(){
configTime(tzString.c_str(), ntpServer.c_str()); configTime(tzString.c_str(), ntpServer.c_str());
} }
// Zeit aus der RTC uebernehmen. Die RTC laeuft in UTC.
#define RTC_INTERVAL_MS 3600000UL // ohne NTP: RTC stuendlich neu einlesen
#define NTP_GUELTIG_MS 7200000UL // NTP-Sync gilt 2 h als aktuell
unsigned long lastRtcReadMs = 0;
unsigned long lastNtpMs = 0; // 0 = noch kein NTP-Sync seit Start
bool ntpShowing = false; // NTP-Wort leuchtet gerade
unsigned long ntpShowUntil = 0;
bool ntpAktuell(){
return lastNtpMs != 0 && millis() - lastNtpMs < NTP_GUELTIG_MS;
}
// Liest die RTC; liefert false, wenn sie keine gueltige Zeit hat (Batterie leer).
bool readRtcToSystem(){
#ifdef RTC_VORHANDEN
lastRtcReadMs = millis();
#if defined(RTC_MODUL_DS3231)
bool valid = !rtc.lostPower();
#else
bool valid = rtc.isrunning();
#endif
DateTime n = rtc.now();
if(valid && n.year() >= 2024){
struct timeval tv = { (time_t)n.unixtime(), 0 };
settimeofday(&tv, nullptr);
ntpJustSynced = false; // der eigene Aufruf ist keine NTP-Zeit
return true;
}
#endif
return false;
}
void initRtc(){
#ifdef RTC_VORHANDEN
Wire.begin(RTC_SDA, RTC_SCL);
if(!rtc.begin()) return;
rtcOk = true;
// Nach dem Nachstellen per NTP ist der Batteriestatus nicht mehr erkennbar,
// daher wird er nur beim Start bestimmt.
rtcBatteryEmpty = !readRtcToSystem();
#endif
}
// Nach jeder NTP-Synchronisation die RTC nachstellen; ohne aktuellen NTP-Sync
// stuendlich die Systemzeit aus der RTC auffrischen.
void updateTimeSources(){
if(ntpJustSynced){
ntpJustSynced = false;
if(timeSynced()){
lastNtpMs = millis() ? millis() : 1;
ntpShowing = true;
ntpShowUntil = millis() + (unsigned long)ntpShowSec * 1000UL;
#ifdef RTC_VORHANDEN
if(rtcOk) rtc.adjust(DateTime((uint32_t)time(nullptr)));
#endif
}
}
if(ntpShowing && (long)(millis() - ntpShowUntil) >= 0) ntpShowing = false;
#ifdef RTC_VORHANDEN
if(rtcOk && !rtcBatteryEmpty && !ntpAktuell() && millis() - lastRtcReadMs >= RTC_INTERVAL_MS)
readRtcToSystem();
#endif
}
// ================= STATUS-WOERTER =================
// NTP (gruen): waehrend/kurz nach dem NTP-Abgleich
// RTC (gelb): RTC vorhanden, aber kein aktueller NTP-Abgleich; bei leerer Batterie
// blinkt das Wort im 10-s-Takt
// ESP (rot): weder gueltige RTC noch NTP
#define STATUS_GRUEN 0x00FF00
#define STATUS_GELB 0xFFFF00
#define STATUS_ROT 0xFF0000
struct StatusFarben { uint32_t ntp, rtc, esp; };
StatusFarben statusAktuell = {0, 0, 0};
StatusFarben berechneStatus(){
StatusFarben st = {0, 0, 0};
if(!statusLeds) return st;
bool ntpOk = ntpAktuell();
bool rtcGut = rtcOk && !rtcBatteryEmpty;
if(ntpShowing) st.ntp = STATUS_GRUEN;
if(rtcOk){
if(rtcBatteryEmpty) st.rtc = ((millis() / 10000UL) & 1) ? 0 : STATUS_GELB;
else if(!ntpOk) st.rtc = STATUS_GELB;
}
if(!rtcGut && !ntpOk) st.esp = STATUS_ROT;
return st;
}
// Aenderung der Statusanzeige erkennen (loest ein Neuzeichnen aus)
bool statusGeaendert(){
StatusFarben st = berechneStatus();
bool changed = st.ntp != statusAktuell.ntp || st.rtc != statusAktuell.rtc || st.esp != statusAktuell.esp;
statusAktuell = st;
return changed;
}
void applyStatus(){
for(int i=34;i<=36;i++) if(statusAktuell.ntp) targetBuffer[i] = statusAktuell.ntp;
for(int i=56;i<=58;i++) if(statusAktuell.rtc) targetBuffer[i] = statusAktuell.rtc;
for(int i=67;i<=69;i++) if(statusAktuell.esp) targetBuffer[i] = statusAktuell.esp;
}
// ================= HELFER ================= // ================= HELFER =================
bool isNightTime(struct tm *t){ bool isNightTime(struct tm *t){
if(!autoNight) return false; if(!autoNight) return false;
@@ -322,34 +181,29 @@ void fadeToTargetSmooth(int steps = 30, int delayMs = 15){
} }
// ================= WORTUHR LOGIK ================= // ================= WORTUHR LOGIK =================
// LED-Indizes in Kettenreihenfolge (Zeilen im Zickzack, Woerter je Zeile in Laufrichtung) void word_ES(){ setRange(0,1); }
void word_ES(){ setRange(0,1); } // Zeile 0 (links->rechts) void word_IST(){ setRange(3,5); }
void word_IST(){ setRange(2,4); } void word_FUENF_M(){ setRange(7,10); }
void word_FUENF_M(){ setRange(5,8); } void word_ZEHN_M(){ setRange(18,21); }
void word_ZEHN_M(){ setRange(9,12); } // Zeile 1 (rechts->links) void word_ZWANZIG(){ setRange(11,17); }
void word_ZWANZIG(){ setRange(13,19); } void word_VIERTEL(){ setRange(22,28); }
void word_VIERTEL(){ setRange(20,26); } // Zeile 2 void word_VOR(){ setRange(30,32); }
void word_VOR(){ setRange(27,29); } void word_NACH(){ setRange(40,43); }
void word_NACH(){ setRange(30,33); } // Zeile 3 (rechts->links) void word_HALB(){ setRange(33,36); }
// NTP 34-36 void word_EINS(){ setRange(62,65); }
void word_HALB(){ setRange(37,40); } void word_EIN(){ setRange(62,64); }
void word_NEUN(){ setRange(41,44); } // Zeile 4 void word_ZWEI(){ setRange(55,58); }
// API 45-47 void word_DREI(){ setRange(66,69); }
void word_FUENF_H(){ setRange(48,51); } void word_VIER(){ setRange(73,76); }
void word_EINS(){ setRange(52,55); } // Zeile 5 (rechts->links): S, EIN, RTC, ZWEI void word_FUENF_H(){ setRange(51,54); }
void word_EIN(){ setRange(53,55); } void word_SECHS(){ setRange(83,87); }
// RTC 56-58 void word_SIEBEN(){ setRange(88,93); }
void word_ZWEI(){ setRange(59,62); } void word_ACHT(){ setRange(77,80); }
void word_DREI(){ setRange(63,66); } // Zeile 6 void word_NEUN(){ setRange(44,47); }
// ESP 67-69 void word_ZEHN_H(){ setRange(106,109); }
void word_VIER(){ setRange(70,73); } void word_ELF(){ setRange(103,105); }
void word_SECHS(){ setRange(74,78); } // Zeile 7 (rechts->links) void word_ZWOELF(){ setRange(94,98); }
void word_ACHT(){ setRange(79,82); } void word_UHR(){ setRange(99,101); }
void word_SIEBEN(){ setRange(83,88); } // Zeile 8
void word_ZWOELF(){ setRange(89,93); }
void word_ZEHN_H(){ setRange(94,97); } // Zeile 9 (rechts->links)
void word_ELF(){ setRange(98,100); }
void word_UHR(){ setRange(101,103); }
String getTimeText(int h, int m){ String getTimeText(int h, int m){
int m5 = (m/5)*5; int m5 = (m/5)*5;
@@ -411,21 +265,10 @@ void showTimeWords(int h, int m){
else if(hh == 7) word_SIEBEN(); else if(hh == 8) word_ACHT(); else if(hh == 9) word_NEUN(); 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(); else if(hh == 10) word_ZEHN_H(); else if(hh == 11) word_ELF(); else word_ZWOELF();
applyStatus();
if(forceInstant){ showInstant(); forceInstant = false; } if(forceInstant){ showInstant(); forceInstant = false; }
else { fadeToTargetSmooth(20, 10); } else { fadeToTargetSmooth(20, 10); }
} }
// Solange noch keine Zeit bekannt ist, nur Hintergrund und Statuswoerter zeigen
void showStatusOnly(){
for(int i=0;i<NUMPIXELS;i++) currentBuffer[i] = targetBuffer[i];
uint32_t activeBg = backgroundMode ? bgColor : 0;
for(int i=0;i<NUMPIXELS;i++) targetBuffer[i] = activeBg;
applyStatus();
showInstant();
}
// ================= WEB HANDLERS ================= // ================= WEB HANDLERS =================
String jsonEscape(const String &s){ String jsonEscape(const String &s){
String out; String out;
@@ -470,9 +313,7 @@ void handleStatus(){
json += "\"nightStart\":" + String(nightStart) + ","; json += "\"nightStart\":" + String(nightStart) + ",";
json += "\"nightEnd\":" + String(nightEnd) + ","; json += "\"nightEnd\":" + String(nightEnd) + ",";
json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ","; json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ",";
json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0) + ","; json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0);
json += "\"statusLeds\":" + String(statusLeds ? 1 : 0) + ",";
json += "\"ntpShowSec\":" + String(ntpShowSec);
json += "}"; json += "}";
server.send(200, "application/json", json); server.send(200, "application/json", json);
} }
@@ -510,10 +351,6 @@ static const char INDEX_HTML[] PROGMEM = R"rawliteral(
<input type='range' id='nbr' min='0' max='255' oninput="f('/nightBrightness?val='+this.value)"> <input type='range' id='nbr' min='0' max='255' oninput="f('/nightBrightness?val='+this.value)">
<input type='checkbox' id='nightBgOff' onchange="f('/nightBgOff?val='+(this.checked?1:0))"> Hintergrund in Nacht aus <input type='checkbox' id='nightBgOff' onchange="f('/nightBgOff?val='+(this.checked?1:0))"> Hintergrund in Nacht aus
</div> </div>
<div class='card'>
<input type='checkbox' id='sl' onchange="f('/statusLeds?val='+(this.checked?1:0))"> <b>Status-Anzeige</b> (NTP gr&uuml;n, RTC gelb, ESP rot)<br><br>
NTP-Anzeige mind. <input type='number' id='nsec' min='1' max='60' style='width:50px' oninput="if(this.value!=='')f('/ntpShowSec?val='+this.value)"> Sekunden
</div>
<div class='card'> <div class='card'>
<label>NTP Server:</label> <label>NTP Server:</label>
<input type='text' id='ntpS' onchange="f('/setNTP?val='+encodeURIComponent(this.value))"> <input type='text' id='ntpS' onchange="f('/setNTP?val='+encodeURIComponent(this.value))">
@@ -530,7 +367,7 @@ static const char INDEX_HTML[] PROGMEM = R"rawliteral(
const s=(i,v,c=false)=>{const e=document.getElementById(i);if(e&&document.activeElement!==e){if(c)e.checked=(v==1);else e.value=v;}}; const s=(i,v,c=false)=>{const e=document.getElementById(i);if(e&&document.activeElement!==e){if(c)e.checked=(v==1);else e.value=v;}};
s('col',d.color);s('br',d.brightness);s('night',d.night,true);s('nS',d.nightStart);s('nE',d.nightEnd); s('col',d.color);s('br',d.brightness);s('night',d.night,true);s('nS',d.nightStart);s('nE',d.nightEnd);
s('nbr',d.nightBrightness);s('bg',d.bg,true);s('bgcol',d.bgColor);s('ntpS',d.ntpServer);s('tzS',d.tzString); s('nbr',d.nightBrightness);s('bg',d.bg,true);s('bgcol',d.bgColor);s('ntpS',d.ntpServer);s('tzS',d.tzString);
s('nightBgOff',d.nightBgOff,true);s('sl',d.statusLeds,true);s('nsec',d.ntpShowSec); s('nightBgOff',d.nightBgOff,true);
}).catch(()=>{document.getElementById('status').innerText='Verbindung unterbrochen...';})} }).catch(()=>{document.getElementById('status').innerText='Verbindung unterbrochen...';})}
setInterval(u, 2500); u(); setInterval(u, 2500); u();
</script></body></html> </script></body></html>
@@ -560,8 +397,6 @@ void handleNightStart(){ int v; if(!argInRange(0,23,v)) return; nightStart=v; fo
void handleNightEnd(){ int v; if(!argInRange(0,23,v)) return; nightEnd=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 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 handleNightBgOff(){ int v; if(!argInRange(0,1,v)) return; nightBackgroundDisabled=v; forceInstant=true; updateNeeded=true; sendOK(); }
void handleStatusLeds(){ int v; if(!argInRange(0,1,v)) return; statusLeds=v; forceInstant=true; updateNeeded=true; sendOK(); }
void handleNtpShowSec(){ int v; if(!argInRange(1,60,v)) return; ntpShowSec=v; sendOK(); }
void handleBG(){ int v; if(!argInRange(0,1,v)) return; backgroundMode=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(){ void handleNTP(){
@@ -587,9 +422,7 @@ void setup(){
pixels.clear(); pixels.clear();
pixels.show(); pixels.show();
settimeofday_cb(onTimeSet);
loadSettings(); loadSettings();
initRtc();
WiFiManager wm; WiFiManager wm;
wm.autoConnect("Wortuhr"); wm.autoConnect("Wortuhr");
@@ -605,8 +438,6 @@ void setup(){
server.on("/nightBrightness", handleNightBrightness); server.on("/nightBrightness", handleNightBrightness);
server.on("/nightBgOff", handleNightBgOff); server.on("/nightBgOff", handleNightBgOff);
server.on("/bg", handleBG); server.on("/bg", handleBG);
server.on("/statusLeds", handleStatusLeds);
server.on("/ntpShowSec", handleNtpShowSec);
server.on("/setNTP", handleNTP); server.on("/setNTP", handleNTP);
server.on("/setTZ", handleTZ); server.on("/setTZ", handleTZ);
server.on("/save", handleSave); server.on("/save", handleSave);
@@ -615,14 +446,9 @@ void setup(){
void loop(){ void loop(){
server.handleClient(); server.handleClient();
updateTimeSources();
if(statusGeaendert()){ forceInstant = true; updateNeeded = true; }
// Ohne bekannte Zeit nur die Statuswoerter anzeigen // Erst anzeigen, wenn die Zeit per NTP synchronisiert ist
if(!timeSynced()){ if(!timeSynced()) return;
if(updateNeeded){ updateNeeded = false; forceInstant = false; showStatusOnly(); }
return;
}
time_t now = time(nullptr); time_t now = time(nullptr);
struct tm *t = localtime(&now); struct tm *t = localtime(&now);