Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
119 changes: 84 additions & 35 deletions wled00/ntp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ bool checkNTPResponse()
// if time changed re-calculate sunrise/sunset
updateLocalTime();
calculateSunriseAndSunset();
// Apply most recent timer preset on first NTP sync after boot
applyBootTimerPreset();
Comment thread
softhack007 marked this conversation as resolved.
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)
Comment thread
softhack007 marked this conversation as resolved.
{
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;
Comment thread
softhack007 marked this conversation as resolved.

// 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