diff --git a/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino b/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino index 3f1a83a..69d0d5c 100644 --- a/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino +++ b/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino @@ -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; diff --git a/ESPTimeCast_ESP32/data/index.html b/ESPTimeCast_ESP32/data/index.html index fc1ab12..12e1de2 100644 --- a/ESPTimeCast_ESP32/data/index.html +++ b/ESPTimeCast_ESP32/data/index.html @@ -558,8 +558,8 @@ textarea::placeholder { - +