mirror of
https://github.com/mfactory-osaka/ESPTimeCast.git
synced 2026-02-19 11:54:56 -05:00
Https API support, screen off mode, password length update
- Added https support for openweathermap - Screen can be turn Off with the brightness slider - Password length has been updated to meet the standard requirements
This commit is contained in:
@@ -33,7 +33,7 @@ const int IP_SCROLL_SPEED = 115; // Default: Adjust this for the IP Address
|
||||
|
||||
// WiFi and configuration globals
|
||||
char ssid[32] = "";
|
||||
char password[32] = "";
|
||||
char password[64] = "";
|
||||
char openWeatherApiKey[64] = "";
|
||||
char openWeatherCity[64] = "";
|
||||
char openWeatherCountry[64] = "";
|
||||
@@ -46,6 +46,7 @@ String detailedDesc = "";
|
||||
// Timing and display settings
|
||||
unsigned long clockDuration = 10000;
|
||||
unsigned long weatherDuration = 5000;
|
||||
bool displayOff = false;
|
||||
int brightness = 7;
|
||||
bool flipDisplay = false;
|
||||
bool twelveHourToggle = false;
|
||||
@@ -58,6 +59,8 @@ char ntpServer2[256] = "time.nist.gov";
|
||||
|
||||
// Dimming
|
||||
bool dimmingEnabled = false;
|
||||
bool displayOffByDimming = false;
|
||||
bool displayOffByBrightness = false;
|
||||
int dimStartHour = 18; // 6pm default
|
||||
int dimStartMinute = 0;
|
||||
int dimEndHour = 8; // 8am default
|
||||
@@ -809,11 +812,36 @@ void setupWebServer() {
|
||||
return;
|
||||
}
|
||||
int newBrightness = request->getParam("value", true)->value().toInt();
|
||||
|
||||
// Handle "off" request
|
||||
if (newBrightness == -1) {
|
||||
P.displayShutdown(true); // Fully shut down display driver
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
Serial.println("[WEBSERVER] Display set to OFF (shutdown mode)");
|
||||
request->send(200, "application/json", "{\"ok\":true, \"display\":\"off\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp brightness to valid range
|
||||
if (newBrightness < 0) newBrightness = 0;
|
||||
if (newBrightness > 15) newBrightness = 15;
|
||||
brightness = newBrightness;
|
||||
P.setIntensity(brightness);
|
||||
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
|
||||
|
||||
// Only run robust clear/reset when coming from "off"
|
||||
if (displayOff) {
|
||||
P.setIntensity(newBrightness);
|
||||
advanceDisplayModeSafe();
|
||||
P.displayShutdown(false);
|
||||
brightness = newBrightness;
|
||||
displayOff = false;
|
||||
Serial.println("[WEBSERVER] Display woke from OFF");
|
||||
} else {
|
||||
// Display already on, just set brightness
|
||||
brightness = newBrightness;
|
||||
P.setIntensity(brightness);
|
||||
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
|
||||
}
|
||||
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
@@ -1164,7 +1192,7 @@ bool isFiveDigitZip(const char *str) {
|
||||
// Weather Fetching and API settings
|
||||
// -----------------------------------------------------------------------------
|
||||
String buildWeatherURL() {
|
||||
String base = "http://api.openweathermap.org/data/2.5/weather?";
|
||||
String base = "https://api.openweathermap.org/data/2.5/weather?";
|
||||
|
||||
float lat = atof(openWeatherCity);
|
||||
float lon = atof(openWeatherCountry);
|
||||
@@ -1219,8 +1247,9 @@ void fetchWeather() {
|
||||
Serial.print(F("[WEATHER] URL: ")); // Use F() with Serial.print
|
||||
Serial.println(url);
|
||||
|
||||
HTTPClient http; // Create an HTTPClient object
|
||||
WiFiClient client; // Create a WiFiClient object
|
||||
HTTPClient http; // Create an HTTPClient object
|
||||
WiFiClientSecure client; // use secure client for HTTPS
|
||||
client.setInsecure(); // disable certificate validation
|
||||
|
||||
http.begin(client, url); // Pass the WiFiClient object and the URL
|
||||
|
||||
@@ -1365,7 +1394,8 @@ void advanceDisplayMode() {
|
||||
} else if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Clock)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful &&
|
||||
countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Clock, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1379,7 +1409,8 @@ void advanceDisplayMode() {
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Date)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful &&
|
||||
countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Date, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1393,7 +1424,8 @@ void advanceDisplayMode() {
|
||||
if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
|
||||
displayMode = 2;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: DESCRIPTION (from Weather)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful &&
|
||||
countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Weather)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1404,7 +1436,8 @@ void advanceDisplayMode() {
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Weather)"));
|
||||
}
|
||||
} else if (displayMode == 2) { // Weather Description
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful &&
|
||||
countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Description)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1431,6 +1464,40 @@ void advanceDisplayMode() {
|
||||
lastSwitch = millis();
|
||||
}
|
||||
|
||||
void advanceDisplayModeSafe() {
|
||||
int attempts = 0;
|
||||
const int MAX_ATTEMPTS = 6; // Number of possible modes + 1
|
||||
int startMode = displayMode;
|
||||
bool valid = false;
|
||||
do {
|
||||
advanceDisplayMode(); // One step advance
|
||||
attempts++;
|
||||
// Recalculate validity for the new mode
|
||||
valid = false;
|
||||
String ntpField = String(ntpServer2);
|
||||
bool nightscoutConfigured = ntpField.startsWith("https://");
|
||||
|
||||
if (displayMode == 0) valid = true; // Clock always valid
|
||||
else if (displayMode == 5 && showDate) valid = true;
|
||||
else if (displayMode == 1 && weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) valid = true;
|
||||
else if (displayMode == 2 && showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) valid = true;
|
||||
else if (displayMode == 3 && countdownEnabled && !countdownFinished && ntpSyncSuccessful) valid = true;
|
||||
else if (displayMode == 4 && nightscoutConfigured) valid = true;
|
||||
|
||||
// If we've looped back to where we started, break to avoid infinite loop
|
||||
if (displayMode == startMode) break;
|
||||
|
||||
if (valid) break;
|
||||
} while (attempts < MAX_ATTEMPTS);
|
||||
|
||||
// If no valid mode found, fall back to Clock
|
||||
if (!valid) {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Safe fallback to CLOCK"));
|
||||
}
|
||||
lastSwitch = millis();
|
||||
}
|
||||
|
||||
//config save after countdown finishes
|
||||
bool saveCountdownConfig(bool enabled, time_t targetTimestamp, const String &label) {
|
||||
DynamicJsonDocument doc(2048);
|
||||
@@ -1527,20 +1594,57 @@ void loop() {
|
||||
bool isDimmingActive = false;
|
||||
|
||||
if (dimmingEnabled) {
|
||||
// Determine if dimming is active (overnight-aware)
|
||||
if (startTotal < endTotal) {
|
||||
isDimmingActive = (curTotal >= startTotal && curTotal < endTotal);
|
||||
} else { // Overnight dimming
|
||||
} else {
|
||||
isDimmingActive = (curTotal >= startTotal || curTotal < endTotal);
|
||||
}
|
||||
if (isDimmingActive) {
|
||||
P.setIntensity(dimBrightness);
|
||||
|
||||
int targetBrightness = isDimmingActive ? dimBrightness : brightness;
|
||||
|
||||
if (targetBrightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF (dimming -1)"));
|
||||
P.displayShutdown(true);
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
displayOffByDimming = true;
|
||||
displayOffByBrightness = false;
|
||||
}
|
||||
} else {
|
||||
P.setIntensity(brightness);
|
||||
if (displayOff && displayOffByDimming) {
|
||||
Serial.println(F("[DISPLAY] Waking display (dimming end)"));
|
||||
P.displayShutdown(false);
|
||||
displayOff = false;
|
||||
displayOffByDimming = false;
|
||||
}
|
||||
P.setIntensity(targetBrightness);
|
||||
}
|
||||
} else {
|
||||
P.setIntensity(brightness);
|
||||
// Dimming disabled: just obey brightness slider
|
||||
if (brightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF (brightness -1)"));
|
||||
P.displayShutdown(true);
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
displayOffByBrightness = true;
|
||||
displayOffByDimming = false;
|
||||
}
|
||||
} else {
|
||||
if (displayOff && displayOffByBrightness) {
|
||||
Serial.println(F("[DISPLAY] Waking display (brightness changed)"));
|
||||
P.displayShutdown(false);
|
||||
displayOff = false;
|
||||
displayOffByBrightness = false;
|
||||
}
|
||||
P.setIntensity(brightness);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- IMMEDIATE COUNTDOWN FINISH TRIGGER ---
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful && countdownTargetTimestamp > 0 && now_time >= countdownTargetTimestamp) {
|
||||
countdownFinished = true;
|
||||
@@ -1575,6 +1679,19 @@ void loop() {
|
||||
|
||||
|
||||
|
||||
// --- BRIGHTNESS/OFF CHECK ---
|
||||
if (brightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF"));
|
||||
P.displayShutdown(true); // fully off
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- NTP State Machine ---
|
||||
switch (ntpState) {
|
||||
case NTP_IDLE: break;
|
||||
@@ -1625,7 +1742,7 @@ void loop() {
|
||||
lastNtpRetryAttempt = millis(); // set baseline on first fail
|
||||
}
|
||||
|
||||
unsigned long ntpRetryInterval = firstRetry ? 30000UL : 300000UL; // first retry after 30s, after that every 5 minutes
|
||||
unsigned long ntpRetryInterval = firstRetry ? 30000UL : 300000UL; // first retry after 30s, after that every 5 minutes
|
||||
|
||||
if (millis() - lastNtpRetryAttempt > ntpRetryInterval) {
|
||||
lastNtpRetryAttempt = millis();
|
||||
@@ -1640,7 +1757,6 @@ void loop() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Only advance mode by timer for clock/weather, not description!
|
||||
unsigned long displayDuration = (displayMode == 0) ? clockDuration : weatherDuration;
|
||||
if ((displayMode == 0 || displayMode == 1) && millis() - lastSwitch > displayDuration) {
|
||||
@@ -1728,6 +1844,7 @@ void loop() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Persistent variables (declare near top of file or loop)
|
||||
static int prevDisplayMode = -1;
|
||||
static bool clockScrollDone = false;
|
||||
@@ -1898,7 +2015,6 @@ void loop() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Countdown Display Mode ---
|
||||
if (displayMode == 3 && countdownEnabled && ntpSyncSuccessful) {
|
||||
static int countdownSegment = 0;
|
||||
|
||||
@@ -558,8 +558,8 @@ textarea::placeholder {
|
||||
</label>
|
||||
|
||||
<label style="margin-top: 1.75rem;">Brightness: <span id="brightnessValue">10</span></label>
|
||||
<input style="width: 100%;" type="range" min="0" max="15" name="brightness" id="brightnessSlider" value="10"
|
||||
oninput="brightnessValue.textContent = brightnessSlider.value; setBrightnessLive(this.value);">
|
||||
<input style="width: 100%;" type="range" min="-1" max="15" name="brightness" id="brightnessSlider" value="10"
|
||||
oninput="brightnessValue.textContent = (this.value == -1 ? 'Off' : this.value); setBrightnessLive(this.value);">
|
||||
<br><br><br>
|
||||
|
||||
<label style="display: flex; align-items: center; justify-content: space-between;">
|
||||
@@ -583,8 +583,8 @@ textarea::placeholder {
|
||||
</div>
|
||||
|
||||
<label style="margin-top: 1.75rem;" for="dimBrightness">Dimming Brightness: <span id="dimmingBrightnessValue">2</span></label>
|
||||
<input style="width: 100%;" type="range" min="0" max="15" name="dimming_brightness" id="dimBrightness" value="2"
|
||||
oninput="dimmingBrightnessValue.textContent = dimBrightness.value; ">
|
||||
<input style="width: 100%;" type="range" min="-1" max="15" name="dimming_brightness" id="dimBrightness" value="2"
|
||||
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'off' : this.value);">
|
||||
<br><br><br>
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; justify-content: space-between;">
|
||||
@@ -679,7 +679,7 @@ window.onload = function () {
|
||||
document.getElementById('language').value = data.language || '';
|
||||
// Advanced:
|
||||
document.getElementById('brightnessSlider').value = typeof data.brightness !== "undefined" ? data.brightness : 10;
|
||||
document.getElementById('brightnessValue').textContent = document.getElementById('brightnessSlider').value;
|
||||
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'off' : document.getElementById('brightnessSlider').value);
|
||||
document.getElementById('flipDisplay').checked = !!data.flipDisplay;
|
||||
document.getElementById('ntpServer1').value = data.ntpServer1 || "";
|
||||
document.getElementById('ntpServer2').value = data.ntpServer2 || "";
|
||||
@@ -713,7 +713,7 @@ document.getElementById('dimEndTime').value =
|
||||
|
||||
document.getElementById('dimBrightness').value = (data.dimBrightness !== undefined ? data.dimBrightness : 2);
|
||||
// Then update the span's text content with that value
|
||||
document.getElementById('dimmingBrightnessValue').textContent = document.getElementById('dimBrightness').value;
|
||||
document.getElementById('dimmingBrightnessValue').textContent = (document.getElementById('dimBrightness').value == -1 ? 'Off' : document.getElementById('dimBrightness').value);
|
||||
|
||||
setDimmingFieldsEnabled(!!data.dimmingEnabled);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const int IP_SCROLL_SPEED = 115; // Default: Adjust this for the IP Address
|
||||
|
||||
// WiFi and configuration globals
|
||||
char ssid[32] = "";
|
||||
char password[32] = "";
|
||||
char password[64] = "";
|
||||
char openWeatherApiKey[64] = "";
|
||||
char openWeatherCity[64] = "";
|
||||
char openWeatherCountry[64] = "";
|
||||
@@ -46,6 +46,7 @@ String detailedDesc = "";
|
||||
// Timing and display settings
|
||||
unsigned long clockDuration = 10000;
|
||||
unsigned long weatherDuration = 5000;
|
||||
bool displayOff = false;
|
||||
int brightness = 7;
|
||||
bool flipDisplay = false;
|
||||
bool twelveHourToggle = false;
|
||||
@@ -58,6 +59,8 @@ char ntpServer2[256] = "time.nist.gov";
|
||||
|
||||
// Dimming
|
||||
bool dimmingEnabled = false;
|
||||
bool displayOffByDimming = false;
|
||||
bool displayOffByBrightness = false;
|
||||
int dimStartHour = 18; // 6pm default
|
||||
int dimStartMinute = 0;
|
||||
int dimEndHour = 8; // 8am default
|
||||
@@ -330,7 +333,7 @@ void connectWiFi() {
|
||||
clearWiFiCredentialsInConfig();
|
||||
strlcpy(ssid, "", sizeof(ssid));
|
||||
strlcpy(password, "", sizeof(password));
|
||||
|
||||
|
||||
WiFiMode_t mode = WiFi.getMode();
|
||||
Serial.printf("[WIFI] WiFi mode after setting AP: %s\n",
|
||||
mode == WIFI_OFF ? "OFF" : mode == WIFI_STA ? "STA ONLY"
|
||||
@@ -810,11 +813,36 @@ void setupWebServer() {
|
||||
return;
|
||||
}
|
||||
int newBrightness = request->getParam("value", true)->value().toInt();
|
||||
|
||||
// Handle "off" request
|
||||
if (newBrightness == -1) {
|
||||
P.displayShutdown(true); // Fully shut down display driver
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
Serial.println("[WEBSERVER] Display set to OFF (shutdown mode)");
|
||||
request->send(200, "application/json", "{\"ok\":true, \"display\":\"off\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp brightness to valid range
|
||||
if (newBrightness < 0) newBrightness = 0;
|
||||
if (newBrightness > 15) newBrightness = 15;
|
||||
brightness = newBrightness;
|
||||
P.setIntensity(brightness);
|
||||
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
|
||||
|
||||
// Only run robust clear/reset when coming from "off"
|
||||
if (displayOff) {
|
||||
P.setIntensity(newBrightness);
|
||||
advanceDisplayModeSafe();
|
||||
P.displayShutdown(false);
|
||||
brightness = newBrightness;
|
||||
displayOff = false;
|
||||
Serial.println("[WEBSERVER] Display woke from OFF");
|
||||
} else {
|
||||
// Display already on, just set brightness
|
||||
brightness = newBrightness;
|
||||
P.setIntensity(brightness);
|
||||
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
|
||||
}
|
||||
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
@@ -1163,7 +1191,7 @@ bool isFiveDigitZip(const char *str) {
|
||||
// Weather Fetching and API settings
|
||||
// -----------------------------------------------------------------------------
|
||||
String buildWeatherURL() {
|
||||
String base = "http://api.openweathermap.org/data/2.5/weather?";
|
||||
String base = "https://api.openweathermap.org/data/2.5/weather?";
|
||||
|
||||
float lat = atof(openWeatherCity);
|
||||
float lon = atof(openWeatherCountry);
|
||||
@@ -1217,8 +1245,9 @@ void fetchWeather() {
|
||||
String url = buildWeatherURL();
|
||||
Serial.println(F("[WEATHER] URL: ") + url);
|
||||
|
||||
HTTPClient http; // Create an HTTPClient object
|
||||
WiFiClient client; // Create a WiFiClient object
|
||||
HTTPClient http; // Create an HTTPClient object
|
||||
WiFiClientSecure client; // use secure client for HTTPS
|
||||
client.setInsecure(); // disable certificate validation
|
||||
|
||||
http.begin(client, url); // Pass the WiFiClient object and the URL
|
||||
|
||||
@@ -1361,7 +1390,7 @@ void advanceDisplayMode() {
|
||||
} else if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Clock)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful && countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Clock, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1375,7 +1404,7 @@ void advanceDisplayMode() {
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Date)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful && countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Date, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1389,7 +1418,7 @@ void advanceDisplayMode() {
|
||||
if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
|
||||
displayMode = 2;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: DESCRIPTION (from Weather)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful && countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Weather)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1400,7 +1429,7 @@ void advanceDisplayMode() {
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Weather)"));
|
||||
}
|
||||
} else if (displayMode == 2) { // Weather Description -> ...
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful && countdownTargetTimestamp > 0 && countdownTargetTimestamp > time(nullptr)) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Description)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
@@ -1427,6 +1456,40 @@ void advanceDisplayMode() {
|
||||
lastSwitch = millis();
|
||||
}
|
||||
|
||||
void advanceDisplayModeSafe() {
|
||||
int attempts = 0;
|
||||
const int MAX_ATTEMPTS = 6; // Number of possible modes + 1
|
||||
int startMode = displayMode;
|
||||
bool valid = false;
|
||||
do {
|
||||
advanceDisplayMode(); // One step advance
|
||||
attempts++;
|
||||
// Recalculate validity for the new mode
|
||||
valid = false;
|
||||
String ntpField = String(ntpServer2);
|
||||
bool nightscoutConfigured = ntpField.startsWith("https://");
|
||||
|
||||
if (displayMode == 0) valid = true; // Clock always valid
|
||||
else if (displayMode == 5 && showDate) valid = true;
|
||||
else if (displayMode == 1 && weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) valid = true;
|
||||
else if (displayMode == 2 && showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) valid = true;
|
||||
else if (displayMode == 3 && countdownEnabled && !countdownFinished && ntpSyncSuccessful) valid = true;
|
||||
else if (displayMode == 4 && nightscoutConfigured) valid = true;
|
||||
|
||||
// If we've looped back to where we started, break to avoid infinite loop
|
||||
if (displayMode == startMode) break;
|
||||
|
||||
if (valid) break;
|
||||
} while (attempts < MAX_ATTEMPTS);
|
||||
|
||||
// If no valid mode found, fall back to Clock
|
||||
if (!valid) {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Safe fallback to CLOCK"));
|
||||
}
|
||||
lastSwitch = millis();
|
||||
}
|
||||
|
||||
//config save after countdown finishes
|
||||
bool saveCountdownConfig(bool enabled, time_t targetTimestamp, const String &label) {
|
||||
DynamicJsonDocument doc(2048);
|
||||
@@ -1523,18 +1586,53 @@ void loop() {
|
||||
bool isDimmingActive = false;
|
||||
|
||||
if (dimmingEnabled) {
|
||||
// Determine if dimming is active (overnight-aware)
|
||||
if (startTotal < endTotal) {
|
||||
isDimmingActive = (curTotal >= startTotal && curTotal < endTotal);
|
||||
} else { // Overnight dimming
|
||||
} else {
|
||||
isDimmingActive = (curTotal >= startTotal || curTotal < endTotal);
|
||||
}
|
||||
if (isDimmingActive) {
|
||||
P.setIntensity(dimBrightness);
|
||||
|
||||
int targetBrightness = isDimmingActive ? dimBrightness : brightness;
|
||||
|
||||
if (targetBrightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF (dimming -1)"));
|
||||
P.displayShutdown(true);
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
displayOffByDimming = true;
|
||||
displayOffByBrightness = false;
|
||||
}
|
||||
} else {
|
||||
P.setIntensity(brightness);
|
||||
if (displayOff && displayOffByDimming) {
|
||||
Serial.println(F("[DISPLAY] Waking display (dimming end)"));
|
||||
P.displayShutdown(false);
|
||||
displayOff = false;
|
||||
displayOffByDimming = false;
|
||||
}
|
||||
P.setIntensity(targetBrightness);
|
||||
}
|
||||
} else {
|
||||
P.setIntensity(brightness);
|
||||
// Dimming disabled: just obey brightness slider
|
||||
if (brightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF (brightness -1)"));
|
||||
P.displayShutdown(true);
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
displayOffByBrightness = true;
|
||||
displayOffByDimming = false;
|
||||
}
|
||||
} else {
|
||||
if (displayOff && displayOffByBrightness) {
|
||||
Serial.println(F("[DISPLAY] Waking display (brightness changed)"));
|
||||
P.displayShutdown(false);
|
||||
displayOff = false;
|
||||
displayOffByBrightness = false;
|
||||
}
|
||||
P.setIntensity(brightness);
|
||||
}
|
||||
}
|
||||
|
||||
// --- IMMEDIATE COUNTDOWN FINISH TRIGGER ---
|
||||
@@ -1570,6 +1668,17 @@ void loop() {
|
||||
}
|
||||
|
||||
|
||||
// --- BRIGHTNESS/OFF CHECK ---
|
||||
if (brightness == -1) {
|
||||
if (!displayOff) {
|
||||
Serial.println(F("[DISPLAY] Turning display OFF"));
|
||||
P.displayShutdown(true); // fully off
|
||||
P.displayClear();
|
||||
displayOff = true;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
|
||||
// --- NTP State Machine ---
|
||||
switch (ntpState) {
|
||||
@@ -1621,7 +1730,7 @@ void loop() {
|
||||
lastNtpRetryAttempt = millis(); // set baseline on first fail
|
||||
}
|
||||
|
||||
unsigned long ntpRetryInterval = firstRetry ? 30000UL : 300000UL; // first retry after 30s, after that every 5 minutes
|
||||
unsigned long ntpRetryInterval = firstRetry ? 30000UL : 300000UL; // first retry after 30s, after that every 5 minutes
|
||||
|
||||
if (millis() - lastNtpRetryAttempt > ntpRetryInterval) {
|
||||
lastNtpRetryAttempt = millis();
|
||||
|
||||
@@ -558,8 +558,8 @@ textarea::placeholder {
|
||||
</label>
|
||||
|
||||
<label style="margin-top: 1.75rem;">Brightness: <span id="brightnessValue">10</span></label>
|
||||
<input style="width: 100%;" type="range" min="0" max="15" name="brightness" id="brightnessSlider" value="10"
|
||||
oninput="brightnessValue.textContent = brightnessSlider.value; setBrightnessLive(this.value);">
|
||||
<input style="width: 100%;" type="range" min="-1" max="15" name="brightness" id="brightnessSlider" value="10"
|
||||
oninput="brightnessValue.textContent = (this.value == -1 ? 'Off' : this.value); setBrightnessLive(this.value);">
|
||||
<br><br><br>
|
||||
|
||||
<label style="display: flex; align-items: center; justify-content: space-between;">
|
||||
@@ -583,8 +583,8 @@ textarea::placeholder {
|
||||
</div>
|
||||
|
||||
<label style="margin-top: 1.75rem;" for="dimBrightness">Dimming Brightness: <span id="dimmingBrightnessValue">2</span></label>
|
||||
<input style="width: 100%;" type="range" min="0" max="15" name="dimming_brightness" id="dimBrightness" value="2"
|
||||
oninput="dimmingBrightnessValue.textContent = dimBrightness.value; ">
|
||||
<input style="width: 100%;" type="range" min="-1" max="15" name="dimming_brightness" id="dimBrightness" value="2"
|
||||
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'off' : this.value);">
|
||||
<br><br><br>
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; justify-content: space-between;">
|
||||
@@ -679,7 +679,7 @@ window.onload = function () {
|
||||
document.getElementById('language').value = data.language || '';
|
||||
// Advanced:
|
||||
document.getElementById('brightnessSlider').value = typeof data.brightness !== "undefined" ? data.brightness : 10;
|
||||
document.getElementById('brightnessValue').textContent = document.getElementById('brightnessSlider').value;
|
||||
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'off' : document.getElementById('brightnessSlider').value);
|
||||
document.getElementById('flipDisplay').checked = !!data.flipDisplay;
|
||||
document.getElementById('ntpServer1').value = data.ntpServer1 || "";
|
||||
document.getElementById('ntpServer2').value = data.ntpServer2 || "";
|
||||
@@ -713,7 +713,7 @@ document.getElementById('dimEndTime').value =
|
||||
|
||||
document.getElementById('dimBrightness').value = (data.dimBrightness !== undefined ? data.dimBrightness : 2);
|
||||
// Then update the span's text content with that value
|
||||
document.getElementById('dimmingBrightnessValue').textContent = document.getElementById('dimBrightness').value;
|
||||
document.getElementById('dimmingBrightnessValue').textContent = (document.getElementById('dimBrightness').value == -1 ? 'Off' : document.getElementById('dimBrightness').value);
|
||||
|
||||
setDimmingFieldsEnabled(!!data.dimmingEnabled);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user