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)