Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1220,6 +1221,7 @@ void serializeConfig(JsonObject root) {
end["day"] = timerDayEnd[i];
}
}
timers["aob"] = applyTimerOnBoot;

JsonObject ota = root.createNestedObject("ota");
ota[F("lock")] = otaLock;
Expand Down
2 changes: 2 additions & 0 deletions wled00/data/settings_time.htm
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ <h3>Button actions</h3>
</table>
<a href="https://kno.wled.ge/features/macros/#analog-button" target="_blank">Analog Button setup</a>
<h3>Time-controlled presets</h3>
Apply scheduled preset on boot: <input type="checkbox" name="TB"><br>
<i style="color:#999">When enabled, if you power on after a scheduled time, that preset will be applied automatically.</i><br><br>
<div style="display: inline-block">
<table id="TMT" style="min-width:330px;"></table>
</div>
Expand Down
1 change: 1 addition & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
121 changes: 85 additions & 36 deletions wled00/ntp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
timerDayEnd[i] = request->arg(k).toInt();
}
}
applyTimerOnBoot = request->hasArg(F("TB"));
}

//SECURITY
Expand Down
2 changes: 2 additions & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand Down
1 change: 1 addition & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down