Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d6ee900c | ||
|
|
20fc9092e6 | ||
|
|
deb5f3e500 | ||
|
|
ca0e6afa8f |
No files matched your search
@@ -35,6 +35,9 @@ 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
|
||||||
@@ -45,6 +48,7 @@ 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
|
||||||
|
|||||||
+203
-29
@@ -5,10 +5,40 @@
|
|||||||
#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
|
||||||
#define NUMPIXELS 110
|
// Neue Aufteilung (cad/openscad/gemeinsam.scad): je Wort ein LED-Streifen mit einer
|
||||||
|
// 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 =================
|
||||||
@@ -19,7 +49,7 @@ String ntpServer = DEFAULT_NTP;
|
|||||||
String tzString = DEFAULT_TZ;
|
String tzString = DEFAULT_TZ;
|
||||||
|
|
||||||
// ================= SETTINGS =================
|
// ================= SETTINGS =================
|
||||||
#define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten
|
#define SETTINGS_MAGIC 0x57554853UL // erkennt gueltige EEPROM-Daten
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
@@ -32,6 +62,8 @@ 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;
|
||||||
@@ -41,6 +73,8 @@ 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;
|
||||||
@@ -70,6 +104,8 @@ 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);
|
||||||
@@ -91,6 +127,8 @@ 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);
|
||||||
@@ -101,6 +139,109 @@ 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;
|
||||||
@@ -181,29 +322,34 @@ void fadeToTargetSmooth(int steps = 30, int delayMs = 15){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ================= WORTUHR LOGIK =================
|
// ================= WORTUHR LOGIK =================
|
||||||
void word_ES(){ setRange(0,1); }
|
// LED-Indizes in Kettenreihenfolge (Zeilen im Zickzack, Woerter je Zeile in Laufrichtung)
|
||||||
void word_IST(){ setRange(3,5); }
|
void word_ES(){ setRange(0,1); } // Zeile 0 (links->rechts)
|
||||||
void word_FUENF_M(){ setRange(7,10); }
|
void word_IST(){ setRange(2,4); }
|
||||||
void word_ZEHN_M(){ setRange(18,21); }
|
void word_FUENF_M(){ setRange(5,8); }
|
||||||
void word_ZWANZIG(){ setRange(11,17); }
|
void word_ZEHN_M(){ setRange(9,12); } // Zeile 1 (rechts->links)
|
||||||
void word_VIERTEL(){ setRange(22,28); }
|
void word_ZWANZIG(){ setRange(13,19); }
|
||||||
void word_VOR(){ setRange(30,32); }
|
void word_VIERTEL(){ setRange(20,26); } // Zeile 2
|
||||||
void word_NACH(){ setRange(40,43); }
|
void word_VOR(){ setRange(27,29); }
|
||||||
void word_HALB(){ setRange(33,36); }
|
void word_NACH(){ setRange(30,33); } // Zeile 3 (rechts->links)
|
||||||
void word_EINS(){ setRange(62,65); }
|
// NTP 34-36
|
||||||
void word_EIN(){ setRange(62,64); }
|
void word_HALB(){ setRange(37,40); }
|
||||||
void word_ZWEI(){ setRange(55,58); }
|
void word_NEUN(){ setRange(41,44); } // Zeile 4
|
||||||
void word_DREI(){ setRange(66,69); }
|
// API 45-47
|
||||||
void word_VIER(){ setRange(73,76); }
|
void word_FUENF_H(){ setRange(48,51); }
|
||||||
void word_FUENF_H(){ setRange(51,54); }
|
void word_EINS(){ setRange(52,55); } // Zeile 5 (rechts->links): S, EIN, RTC, ZWEI
|
||||||
void word_SECHS(){ setRange(83,87); }
|
void word_EIN(){ setRange(53,55); }
|
||||||
void word_SIEBEN(){ setRange(88,93); }
|
// RTC 56-58
|
||||||
void word_ACHT(){ setRange(77,80); }
|
void word_ZWEI(){ setRange(59,62); }
|
||||||
void word_NEUN(){ setRange(44,47); }
|
void word_DREI(){ setRange(63,66); } // Zeile 6
|
||||||
void word_ZEHN_H(){ setRange(106,109); }
|
// ESP 67-69
|
||||||
void word_ELF(){ setRange(103,105); }
|
void word_VIER(){ setRange(70,73); }
|
||||||
void word_ZWOELF(){ setRange(94,98); }
|
void word_SECHS(){ setRange(74,78); } // Zeile 7 (rechts->links)
|
||||||
void word_UHR(){ setRange(99,101); }
|
void word_ACHT(){ setRange(79,82); }
|
||||||
|
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;
|
||||||
@@ -265,10 +411,21 @@ 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;
|
||||||
@@ -313,7 +470,9 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -351,6 +510,10 @@ 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ü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))">
|
||||||
@@ -367,7 +530,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('nightBgOff',d.nightBgOff,true);s('sl',d.statusLeds,true);s('nsec',d.ntpShowSec);
|
||||||
}).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>
|
||||||
@@ -397,6 +560,8 @@ 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(){
|
||||||
@@ -422,7 +587,9 @@ 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");
|
||||||
@@ -438,6 +605,8 @@ 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);
|
||||||
@@ -446,9 +615,14 @@ void setup(){
|
|||||||
|
|
||||||
void loop(){
|
void loop(){
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
|
updateTimeSources();
|
||||||
|
if(statusGeaendert()){ forceInstant = true; updateNeeded = true; }
|
||||||
|
|
||||||
// Erst anzeigen, wenn die Zeit per NTP synchronisiert ist
|
// Ohne bekannte Zeit nur die Statuswoerter anzeigen
|
||||||
if(!timeSynced()) return;
|
if(!timeSynced()){
|
||||||
|
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);
|
||||||
|
|||||||
@@ -1,8 +1,40 @@
|
|||||||
[env:d1_mini]
|
; Umgebung = Board + RTC-Modul waehlen, z.B.: pio run -e nodemcu_ds3231 -t upload
|
||||||
|
; 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
|
||||||
+203
-29
@@ -5,10 +5,40 @@
|
|||||||
#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
|
||||||
#define NUMPIXELS 110
|
// Neue Aufteilung (cad/openscad/gemeinsam.scad): je Wort ein LED-Streifen mit einer
|
||||||
|
// 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 =================
|
||||||
@@ -19,7 +49,7 @@ String ntpServer = DEFAULT_NTP;
|
|||||||
String tzString = DEFAULT_TZ;
|
String tzString = DEFAULT_TZ;
|
||||||
|
|
||||||
// ================= SETTINGS =================
|
// ================= SETTINGS =================
|
||||||
#define SETTINGS_MAGIC 0x57554852UL // erkennt gueltige EEPROM-Daten
|
#define SETTINGS_MAGIC 0x57554853UL // erkennt gueltige EEPROM-Daten
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
@@ -32,6 +62,8 @@ 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;
|
||||||
@@ -41,6 +73,8 @@ 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;
|
||||||
@@ -70,6 +104,8 @@ 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);
|
||||||
@@ -91,6 +127,8 @@ 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);
|
||||||
@@ -101,6 +139,109 @@ 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;
|
||||||
@@ -181,29 +322,34 @@ void fadeToTargetSmooth(int steps = 30, int delayMs = 15){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ================= WORTUHR LOGIK =================
|
// ================= WORTUHR LOGIK =================
|
||||||
void word_ES(){ setRange(0,1); }
|
// LED-Indizes in Kettenreihenfolge (Zeilen im Zickzack, Woerter je Zeile in Laufrichtung)
|
||||||
void word_IST(){ setRange(3,5); }
|
void word_ES(){ setRange(0,1); } // Zeile 0 (links->rechts)
|
||||||
void word_FUENF_M(){ setRange(7,10); }
|
void word_IST(){ setRange(2,4); }
|
||||||
void word_ZEHN_M(){ setRange(18,21); }
|
void word_FUENF_M(){ setRange(5,8); }
|
||||||
void word_ZWANZIG(){ setRange(11,17); }
|
void word_ZEHN_M(){ setRange(9,12); } // Zeile 1 (rechts->links)
|
||||||
void word_VIERTEL(){ setRange(22,28); }
|
void word_ZWANZIG(){ setRange(13,19); }
|
||||||
void word_VOR(){ setRange(30,32); }
|
void word_VIERTEL(){ setRange(20,26); } // Zeile 2
|
||||||
void word_NACH(){ setRange(40,43); }
|
void word_VOR(){ setRange(27,29); }
|
||||||
void word_HALB(){ setRange(33,36); }
|
void word_NACH(){ setRange(30,33); } // Zeile 3 (rechts->links)
|
||||||
void word_EINS(){ setRange(62,65); }
|
// NTP 34-36
|
||||||
void word_EIN(){ setRange(62,64); }
|
void word_HALB(){ setRange(37,40); }
|
||||||
void word_ZWEI(){ setRange(55,58); }
|
void word_NEUN(){ setRange(41,44); } // Zeile 4
|
||||||
void word_DREI(){ setRange(66,69); }
|
// API 45-47
|
||||||
void word_VIER(){ setRange(73,76); }
|
void word_FUENF_H(){ setRange(48,51); }
|
||||||
void word_FUENF_H(){ setRange(51,54); }
|
void word_EINS(){ setRange(52,55); } // Zeile 5 (rechts->links): S, EIN, RTC, ZWEI
|
||||||
void word_SECHS(){ setRange(83,87); }
|
void word_EIN(){ setRange(53,55); }
|
||||||
void word_SIEBEN(){ setRange(88,93); }
|
// RTC 56-58
|
||||||
void word_ACHT(){ setRange(77,80); }
|
void word_ZWEI(){ setRange(59,62); }
|
||||||
void word_NEUN(){ setRange(44,47); }
|
void word_DREI(){ setRange(63,66); } // Zeile 6
|
||||||
void word_ZEHN_H(){ setRange(106,109); }
|
// ESP 67-69
|
||||||
void word_ELF(){ setRange(103,105); }
|
void word_VIER(){ setRange(70,73); }
|
||||||
void word_ZWOELF(){ setRange(94,98); }
|
void word_SECHS(){ setRange(74,78); } // Zeile 7 (rechts->links)
|
||||||
void word_UHR(){ setRange(99,101); }
|
void word_ACHT(){ setRange(79,82); }
|
||||||
|
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;
|
||||||
@@ -265,10 +411,21 @@ 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;
|
||||||
@@ -313,7 +470,9 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -351,6 +510,10 @@ 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ü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))">
|
||||||
@@ -367,7 +530,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('nightBgOff',d.nightBgOff,true);s('sl',d.statusLeds,true);s('nsec',d.ntpShowSec);
|
||||||
}).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>
|
||||||
@@ -397,6 +560,8 @@ 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(){
|
||||||
@@ -422,7 +587,9 @@ 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");
|
||||||
@@ -438,6 +605,8 @@ 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);
|
||||||
@@ -446,9 +615,14 @@ void setup(){
|
|||||||
|
|
||||||
void loop(){
|
void loop(){
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
|
updateTimeSources();
|
||||||
|
if(statusGeaendert()){ forceInstant = true; updateNeeded = true; }
|
||||||
|
|
||||||
// Erst anzeigen, wenn die Zeit per NTP synchronisiert ist
|
// Ohne bekannte Zeit nur die Statuswoerter anzeigen
|
||||||
if(!timeSynced()) return;
|
if(!timeSynced()){
|
||||||
|
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);
|
||||||
|
|||||||
Reference in new issue
Block a user