diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index ff491faffd..420065037e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -712,6 +712,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } it++; } + CJSON(applyTimerOnBoot, tm["aob"]); JsonObject ota = doc["ota"]; const char* pwd = ota["psk"]; //normally not present due to security @@ -1220,6 +1221,7 @@ void serializeConfig(JsonObject root) { end["day"] = timerDayEnd[i]; } } + timers["aob"] = applyTimerOnBoot; JsonObject ota = root.createNestedObject("ota"); ota[F("lock")] = otaLock; diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index 11f3c47d9d..3e5644daff 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -210,6 +210,8 @@

Button actions

Analog Button setup

Time-controlled presets

+ Apply scheduled preset on boot:
+ When enabled, if you power on after a scheduled time, that preset will be applied automatically.

diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 84b5595df7..436eccd0d2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -202,6 +202,7 @@ void setCountdown(); byte weekdayMondayFirst(); bool isTodayInDateRange(byte monthStart, byte dayStart, byte monthEnd, byte dayEnd); void checkTimers(); +void applyBootTimerPreset(); void calculateSunriseAndSunset(); void setTimeFromAPI(uint32_t timein); diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index abad5c3c9d..7c6557f1c1 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -289,9 +289,11 @@ bool checkNTPResponse() #endif if (countdownTime - toki.second() > 0) countdownOverTriggered = false; - // if time changed re-calculate sunrise/sunset + + // NTP sync succeeded updateLocalTime(); calculateSunriseAndSunset(); + applyBootTimerPreset(); return true; } @@ -376,6 +378,39 @@ bool isTodayInDateRange(byte monthStart, byte dayStart, byte monthEnd, byte dayE return (m == monthStart && d >= dayStart && d <= dayEnd); //just the designated days this month } +/* + * Returns the minute-of-day (0-1439) for timer i if it's valid for today, or -1 if invalid. + * Validates: preset assigned, timer enabled, weekday matches, date range (for timers 0-7). + * For "every hour" timers (hour=24), returns the current hour's scheduled minute. + * For sunrise/sunset timers (8/9), returns the calculated trigger time. + */ +static int getTimerMinuteOfDay(unsigned i) +{ + if (i > 9) return -1; + if (timerMacro[i] == 0) return -1; + if (!(timerWeekday[i] & 0x01)) return -1; // not enabled + if (!((timerWeekday[i] >> weekdayMondayFirst()) & 0x01)) return -1; // wrong weekday + + if (i < 8) { + // Standard timer (0-7) + if (!isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i])) return -1; + if (timerHours[i] == 24) { + return hour(localTime) * 60 + timerMinutes[i]; // "every hour" at this minute + } + return timerHours[i] * 60 + timerMinutes[i]; + } else if (i == 8) { + // Sunrise timer + if (!sunrise) return -1; + time_t t = sunrise + timerMinutes[8] * 60; + return hour(t) * 60 + minute(t); + } else { + // Sunset timer (i == 9) + if (!sunset) return -1; + time_t t = sunset + timerMinutes[9] * 60; + return hour(t) * 60 + minute(t); + } +} + void checkTimers() { if (lastTimerMinute != minute(localTime)) //only check once a new minute begins @@ -385,49 +420,63 @@ void checkTimers() // re-calculate sunrise and sunset just after midnight if (!hour(localTime) && minute(localTime)==1) calculateSunriseAndSunset(); + int currentMinuteOfDay = elapsedSecsToday(localTime) / 60; DEBUG_PRINTF_P(PSTR("Local time: %02d:%02d\n"), hour(localTime), minute(localTime)); - for (unsigned i = 0; i < 8; i++) + + for (unsigned i = 0; i < 10; i++) { - if (timerMacro[i] != 0 - && (timerWeekday[i] & 0x01) //timer is enabled - && (timerHours[i] == hour(localTime) || timerHours[i] == 24) //if hour is set to 24, activate every hour - && timerMinutes[i] == minute(localTime) - && ((timerWeekday[i] >> weekdayMondayFirst()) & 0x01) //timer should activate at current day of week - && isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i]) - ) - { + int timerMinute = getTimerMinuteOfDay(i); // returns -1 if timer not valid for today + if (timerMinute == currentMinuteOfDay) { applyPreset(timerMacro[i]); + if (i == 8) DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."), timerMacro[8]); + if (i == 9) DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."), timerMacro[9]); } } - // sunrise macro - if (sunrise) { - time_t tmp = sunrise + timerMinutes[8]*60; // NOTE: may not be ok - DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp)); - if (timerMacro[8] != 0 - && hour(tmp) == hour(localTime) - && minute(tmp) == minute(localTime) - && (timerWeekday[8] & 0x01) //timer is enabled - && ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week - { - applyPreset(timerMacro[8]); - DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."),timerMacro[8]); - } + } +} + +/* + * Apply the most recent time-controlled preset that should have triggered today. + * Called once after NTP sync on boot to handle powering on after a scheduled time. + */ +void applyBootTimerPreset() +{ + if (bootTimerApplied || !applyTimerOnBoot) return; + bootTimerApplied = true; + + int currentMinuteOfDay = elapsedSecsToday(localTime) / 60; + int latestTimerMinute = -1; + byte latestPreset = 0; + + DEBUG_PRINTLN(F("Checking for boot timer preset...")); + + for (unsigned i = 0; i < 10; i++) + { + int timerMinute = getTimerMinuteOfDay(i); // returns -1 if timer not valid for today + if (timerMinute < 0) continue; + + // For "every hour" timers, find the most recent past occurrence + if (i < 8 && timerHours[i] == 24 && timerMinute > currentMinuteOfDay) { + timerMinute -= 60; + if (timerMinute < 0) continue; // would be yesterday, skip } - // sunset macro - if (sunset) { - time_t tmp = sunset + timerMinutes[9]*60; // NOTE: may not be ok - DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp)); - if (timerMacro[9] != 0 - && hour(tmp) == hour(localTime) - && minute(tmp) == minute(localTime) - && (timerWeekday[9] & 0x01) //timer is enabled - && ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week - { - applyPreset(timerMacro[9]); - DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."),timerMacro[9]); - } + + // Only consider timers that should have already triggered today. + // Use >= so that if multiple timers share the same minute, the highest index wins, + // matching checkTimers() behavior where all are applied and the last one sticks. + if (timerMinute <= currentMinuteOfDay && timerMinute >= latestTimerMinute) { + latestTimerMinute = timerMinute; + latestPreset = timerMacro[i]; } } + + if (latestPreset > 0) { + DEBUG_PRINTF_P(PSTR("Applying boot timer preset %d (scheduled for %02d:%02d)\n"), + latestPreset, latestTimerMinute / 60, latestTimerMinute % 60); + applyPreset(latestPreset); + } else { + DEBUG_PRINTLN(F("No applicable boot timer preset found.")); + } } #define ZENITH -0.83 diff --git a/wled00/set.cpp b/wled00/set.cpp index db8b30bac8..f7618f9625 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -568,6 +568,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) timerDayEnd[i] = request->arg(k).toInt(); } } + applyTimerOnBoot = request->hasArg(F("TB")); } //SECURITY diff --git a/wled00/wled.h b/wled00/wled.h index 66b33740d6..25a5ebc20f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -815,6 +815,8 @@ WLED_GLOBAL bool countdownOverTriggered _INIT(true); //timer WLED_GLOBAL byte lastTimerMinute _INIT(0); +WLED_GLOBAL bool bootTimerApplied _INIT(false); // whether boot-time timer check has been performed +WLED_GLOBAL bool applyTimerOnBoot _INIT(false); // apply most recent scheduled timer preset on boot (after NTP sync) WLED_GLOBAL byte timerHours[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); WLED_GLOBAL int8_t timerMinutes[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); WLED_GLOBAL byte timerMacro[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 194256d82e..d1e519ddd3 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -601,6 +601,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) k[0] = 'E'; printSetFormValue(settingsScript,k,timerDayEnd[i]); } } + printSetFormCheckbox(settingsScript,PSTR("TB"),applyTimerOnBoot); } if (subPage == SUBPAGE_SEC)