Skip to content
Closed
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
5 changes: 5 additions & 0 deletions wled00/data/settings_time.htm
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ <h3>Clock</h3>
Countdown Goal:<br>
Date:&nbsp;<nowrap>20<input name="CY" class="xs" type="number" min="0" max="99" required>-<input name="CI" class="xs" type="number" min="1" max="12" required>-<input name="CD" class="xs" type="number" min="1" max="31" required></nowrap><br>
Time:&nbsp;<nowrap><input name="CH" class="xs" type="number" min="0" max="23" required>:<input name="CM" class="xs" type="number" min="0" max="59" required>:<input name="CS" class="xs" type="number" min="0" max="59" required></nowrap><br>
<h3>Upload Schedule JSON</h3>
<input type="file" name="scheduleFile" id="scheduleFile" accept=".json">
<input type="button" value="Upload" onclick="uploadFile(d.Sf.scheduleFile, '/schedule.json');">
<br>
<a class="btn lnk" id="bckschedule" href="/schedule.json" download="schedule">Backup schedule</a><br>
<h3>Macro presets</h3>
<b>Macros have moved!</b><br>
<i>Presets now also can be used as macros to save both JSON and HTTP API commands.<br>
Expand Down
130 changes: 130 additions & 0 deletions wled00/schedule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// schedule.cpp


#include "schedule.h"
#include <WLED.h>
#include <time.h>

#define SCHEDULE_FILE "/schedule.json"

ScheduleEvent scheduleEvents[MAX_SCHEDULE_EVENTS];
uint8_t numScheduleEvents = 0;

bool isTodayInRange(uint8_t sm, uint8_t sd, uint8_t em, uint8_t ed, uint8_t cm, uint8_t cd)
{
if (sm < em || (sm == em && sd <= ed))
{
return (cm > sm || (cm == sm && cd >= sd)) &&
(cm < em || (cm == em && cd <= ed));
}
else
{
return (cm > sm || (cm == sm && cd >= sd)) ||
(cm < em || (cm == em && cd <= ed));
}
}


// Checks the schedule and applies any events that match the current time and date.

void checkSchedule() {
static int lastMinute = -1;

time_t now = localTime;
if (now < 100000) return;

struct tm* timeinfo = localtime(&now);
int thisMinute = timeinfo->tm_min + timeinfo->tm_hour * 60;

if (thisMinute == lastMinute) return;
lastMinute = thisMinute;


uint8_t cm = timeinfo->tm_mon + 1; // months since Jan (0-11)
uint8_t cd = timeinfo->tm_mday;
uint8_t wday = timeinfo->tm_wday; // days since Sunday (0-6)
uint8_t hr = timeinfo->tm_hour;
uint8_t min = timeinfo->tm_min;

DEBUG_PRINTF_P(PSTR("[Schedule] Checking schedule at %02u:%02u\n"), hr, min);

for (uint8_t i = 0; i < numScheduleEvents; i++)
{
const ScheduleEvent &e = scheduleEvents[i];
if (e.hour != hr || e.minute != min)
continue;

bool match = false;
if (e.repeatMask && ((e.repeatMask >> wday) & 0x01))
match = true;
if (e.startMonth)
{
if (isTodayInRange(e.startMonth, e.startDay, e.endMonth, e.endDay, cm, cd))
match = true;
}

if (match)
{
applyPreset(e.presetId);
DEBUG_PRINTF_P(PSTR("[Schedule] Applying preset %u at %02u:%02u\n"), e.presetId, hr, min);
}
}
}

bool loadSchedule() {
if (!WLED_FS.exists(SCHEDULE_FILE)) return false;

if (!requestJSONBufferLock(7)) return false; // 🔐 Acquire lock safely

File file = WLED_FS.open(SCHEDULE_FILE, "r");
if (!file) {
releaseJSONBufferLock();
return false;
}

DynamicJsonDocument doc(4096);
DeserializationError error = deserializeJson(doc, file);
file.close(); // ✅ Always close before releasing lock

if (error) {
DEBUG_PRINTF_P(PSTR("[Schedule] JSON parse failed: %s\n"), error.c_str());
releaseJSONBufferLock();
return false;
}

numScheduleEvents = 0;
for (JsonObject e : doc.as<JsonArray>()) {
if (numScheduleEvents >= MAX_SCHEDULE_EVENTS) break;

// Extract and validate JSON fields before assignment
int sm = e["sm"].as<int>();
int sd = e["sd"].as<int>();
int em = e["em"].as<int>();
int ed = e["ed"].as<int>();
int r = e["r"].as<int>();
int h = e["h"].as<int>();
int m = e["m"].as<int>();
int p = e["p"].as<int>();

// Validate ranges: months 1–12, days 1–31, hours 0–23, minutes 0–59,
// repeat mask 0–127, preset ID 1–250
if (sm < 1 || sm > 12 || em < 1 || em > 12 ||
sd < 1 || sd > 31 || ed < 1 || ed > 31 ||
h < 0 || h > 23 || m < 0 || m > 59 ||
r < 0 || r > 127|| p < 1 || p > 250) {
DEBUG_PRINTF_P(PSTR("[Schedule] Invalid values in event %u, skipping\n"), numScheduleEvents);
continue;
}

scheduleEvents[numScheduleEvents++] = {
(uint8_t)sm, (uint8_t)sd,
(uint8_t)em, (uint8_t)ed,
(uint8_t)r, (uint8_t)h,
(uint8_t)m, (uint8_t)p
};
}

DEBUG_PRINTF_P(PSTR("[Schedule] Loaded %u schedule entries from schedule.json\n"), numScheduleEvents);
releaseJSONBufferLock(); // 🔓 Unlock before returning
return true;
}
20 changes: 20 additions & 0 deletions wled00/schedule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// schedule.h
#pragma once

#include <stdint.h>

#define MAX_SCHEDULE_EVENTS 32

struct ScheduleEvent {
uint8_t startMonth;
uint8_t startDay;
uint8_t endMonth;
uint8_t endDay;
uint8_t repeatMask;
uint8_t hour;
uint8_t minute;
uint8_t presetId;
};

bool loadSchedule();
void checkSchedule();
3 changes: 3 additions & 0 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void WLED::loop()
#endif

handleTime();
checkSchedule();
#ifndef WLED_DISABLE_INFRARED
handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too
#endif
Expand Down Expand Up @@ -526,6 +527,8 @@ void WLED::setup()
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector
#endif

loadSchedule();
}

void WLED::beginStrip()
Expand Down
2 changes: 2 additions & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@

#include <cstddef>
#include <vector>
#include "schedule.h"


// Library inclusions.
#include <Arduino.h>
Expand Down
Loading