Skip to content

Conversation

PagedPenguin
Copy link

@PagedPenguin PagedPenguin commented Jul 13, 2025

Disclaimer: I have basically no experience with doing this kind of thing, I'm just good a hooking ai up to stuff, so I was able to teach myself enough to get by.

This PR introduces a lightweight scheduling system that applies WLED presets automatically based on time and date. The goal is to enable users to automate WLED behavior without needing external automations or integrations.

Purpose
This allows users to:

  • Run presets on specific dates (e.g. holidays, birthdays)
  • Trigger effects at set times weekly (e.g. every Friday at 6 PM)
  • Build repeatable, time-based lighting behaviors with zero network dependencies

It works purely offline, and is simple enough to be used by less technical users via a JSON file (as in people not running home assistant or similar).

How it works

  • A schedule.json can be uploaded via /settings/time
    - The schedule system loads this file on startup and... every minute (TODO: Make the load schedule only trigger on upload and startup [schedule.cpp loadSchedule() function]) Now only loads on startup and upload [37a536a]
  • Each schedule entry contains a time, an optional date range, and/or a weekly repeat bitmask.
  • When the current time matches an entry, the configured preset is applied.
  • The checkSchedule() gets called in the wled.cpp loop() and if it was triggered less than a minute ago doesn't (fully) trigger. (TODO: Implement anti-duplicate trigger better [schedule.cpp lines 32-41])

JSON Formating

  • sm, sd, em, ed: Start/end month & day (date range)
  • r: Weekly repeat bitmask (bit 0 = Sunday, bit 6 = Saturday)
  • h, m: Hour and minute to trigger
  • p: Preset ID to apply
[
  {
    "sm": 7,
    "sd": 4,
    "em": 7,
    "ed": 4,
    "r": 0,
    "h": 20,
    "m": 30,
    "p": 5
  },
  {
    "sm": 0,
    "sd": 0,
    "em": 0,
    "ed": 0,
    "r": 34,
    "h": 7,
    "m": 15,
    "p": 2
  }
]

Explanation:
First entry: Runs preset 5 on July 4th at 20:30 every year.

Second entry: Runs preset 2 every Monday and Friday at 07:15.
(Repeat bitmask 34 = 0b100010 = Monday and Friday)

Testing

  • Verified that schedule.json loads properly at startup
  • Confirmed scheduled presets trigger correctly and only once per minute
  • Logged matched events using DEBUG_PRINTF when WLED_DEBUG is defined
  • Tested both date-based and repeat-based schedules
  • Confirmed working on a nodemcu v2 ESP8266, d1mini ESP8266, and a d1mini ESP32.

Limitations / Future Work

  • [as mentioned above]: (TODO: Implement anti-duplicate trigger better [schedule.cpp lines 32-41]), and (TODO: Make the load schedule only trigger on upload and boot [schedule.cpp loadSchedule() function])
  • No UI for schedule editing yet (future enhancement possible)
  • Does not support second-level precision (minute-level only, by design)
  • Only presets can be triggered (no other macro types or HTTP actions)

Notes for maintainers

  • No core WLED behavior was changed
  • All new code is modular and self-contained in schedule.cpp/.h
  • Can be made into a build flag to be included or not.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a scheduling system that allows users to automate preset activations based on custom schedules defined in a JSON file.
    • Added an interface on the time settings page for uploading and downloading schedule JSON files, enabling easy backup and management of schedules.
  • Enhancements

    • Automated schedule checks and preset activations now run seamlessly in the background.
    • Schedule uploads are now handled safely with temporary files to ensure reliable updates and immediate application of new schedules.

Copy link
Contributor

coderabbitai bot commented Jul 13, 2025

Walkthrough

A scheduling system was implemented, allowing events to trigger presets at specified times based on a JSON schedule file. Supporting code includes a new header, schedule parsing and checking logic, and integration with the main setup and loop routines. The web interface was updated to support uploading and downloading the schedule JSON. The file upload handler was enhanced to safely update the schedule JSON by writing to a temporary file before replacing the original and immediately applying the new schedule.

Changes

Files/Paths Change Summary
wled00/data/settings_time.htm Added "Upload Schedule JSON" section with file input, upload button, and download link for schedule file.
wled00/schedule.cpp, wled00/schedule.h Implemented scheduling system: defined ScheduleEvent struct, JSON schedule loading, event checking, and related functions.
wled00/wled.cpp Called loadSchedule() in setup and checkSchedule() in the main loop.
wled00/wled.h Added #include "schedule.h".
wled00/wled_server.cpp Enhanced file upload handler to write /schedule.json uploads to a temporary file, replace original atomically, and load new schedule immediately; also applied code style improvements.
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
wled00/schedule.h (1)

8-17: Consider documenting the struct fields.

While the struct design is compact and memory-efficient, consider adding brief comments to clarify the purpose of each field, especially repeatMask which uses bit manipulation.

 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;
+  uint8_t startMonth;  // 1-12, or 0 if not used
+  uint8_t startDay;    // 1-31
+  uint8_t endMonth;    // 1-12, or 0 if not used  
+  uint8_t endDay;      // 1-31
+  uint8_t repeatMask;  // bitmask: bit 0=Sunday, bit 1=Monday, etc.
+  uint8_t hour;        // 0-23
+  uint8_t minute;      // 0-59
+  uint8_t presetId;    // preset to apply
 };
wled00/schedule.cpp (1)

83-83: Consider reducing JSON buffer size for memory efficiency.

A 4096-byte buffer may be excessive for the expected schedule JSON size. Consider reducing it or making it configurable based on available memory.

-    DynamicJsonDocument doc(4096);
+    DynamicJsonDocument doc(2048);  // Should be sufficient for 32 events
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7bfd6f and bd0b620.

📒 Files selected for processing (5)
  • wled00/data/settings_time.htm (1 hunks)
  • wled00/schedule.cpp (1 hunks)
  • wled00/schedule.h (1 hunks)
  • wled00/wled.cpp (2 hunks)
  • wled00/wled.h (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
wled00/wled.cpp (1)
Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.
🧬 Code Graph Analysis (2)
wled00/wled.cpp (2)
wled00/schedule.h (2)
  • checkSchedule (20-20)
  • loadSchedule (19-19)
wled00/schedule.cpp (4)
  • checkSchedule (31-73)
  • checkSchedule (31-31)
  • loadSchedule (75-104)
  • loadSchedule (75-75)
wled00/schedule.cpp (1)
wled00/schedule.h (1)
  • loadSchedule (19-19)
🔇 Additional comments (6)
wled00/wled.h (1)

69-69: LGTM - Clean integration of scheduling header.

The inclusion of schedule.h is properly placed and enables the scheduling functionality declared in the new header file.

wled00/wled.cpp (2)

57-57: LGTM - Proper integration of schedule checking.

The placement of checkSchedule() after handleTime() ensures that the current time is available before checking scheduled events. This is the appropriate location in the main loop.


531-531: LGTM - Appropriate initialization of schedule system.

Loading the schedule during setup ensures that scheduled events are available from startup. The placement is correct within the setup sequence.

wled00/data/settings_time.htm (1)

181-185: LGTM - Clean UI integration for schedule management.

The new section provides necessary functionality for users to upload and download schedule JSON files. The file input restriction to .json files and the backup download link are good UX practices.

wled00/schedule.h (1)

6-6: Good choice for embedded systems.

The limit of 32 schedule events strikes a good balance between functionality and memory usage for embedded devices.

wled00/schedule.cpp (1)

14-26: LGTM - Correct date range logic with year-end handling.

The isTodayInRange() function correctly handles date ranges that span across year boundaries (e.g., December to February). The logic properly distinguishes between same-year ranges and cross-year ranges.

Add error handling for the schedule.json

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@PagedPenguin PagedPenguin marked this pull request as draft July 13, 2025 23:22
@PagedPenguin PagedPenguin marked this pull request as ready for review July 14, 2025 00:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9b99af and 37a536a.

📒 Files selected for processing (2)
  • wled00/schedule.cpp (1 hunks)
  • wled00/wled_server.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/schedule.cpp
🧰 Additional context used
🧠 Learnings (1)
wled00/wled_server.cpp (1)
Learnt from: blazoncek
PR: wled/WLED#4700
File: wled00/wled_server.cpp:409-414
Timestamp: 2025-05-26T16:09:34.325Z
Learning: In wled00/wled_server.cpp, the OTA update access control logic intentionally allows OTA updates from different subnets when otaSameSubnet is true AND a PIN is set. This was a conscious design decision by blazoncek to provide flexibility for remote OTA updates with PIN protection, though they acknowledged it may not be the optimal security model.
🔇 Additional comments (2)
wled00/wled_server.cpp (2)

183-200: LGTM: Good filename normalization and special case handling.

The filename normalization ensures consistency, and the special handling for /schedule.json using a temporary file is a good practice for atomic updates.


223-232: LGTM: Proper handling of other file types.

The logic for handling configuration files, palette files, and other uploads is well-structured and maintains existing functionality.

PagedPenguin and others added 2 commits July 13, 2025 19:13
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
wled00/schedule.cpp (1)

66-72: Fix debug statement placement.

The debug statement on line 71 executes for every event regardless of whether it matches, creating misleading log output. This was flagged in previous reviews but remains unfixed.

     if (match)
     {
         applyPreset(e.presetId);
         DEBUG_PRINTF_P(PSTR("[Schedule] Applying preset %u at %02u:%02u\n"), e.presetId, hr, min);
+        DEBUG_PRINTF_P(PSTR("[Schedule] Checked event %u: match=%d\n"), i, match);
     }
-    DEBUG_PRINTF_P(PSTR("[Schedule] Checked event %u: match=%d\n"), i, match);
🧹 Nitpick comments (1)
wled00/schedule.cpp (1)

86-86: Consider stack usage for embedded systems.

A 4096-byte DynamicJsonDocument on the stack may be too large for ESP8266/ESP32 environments, potentially causing stack overflow.

Consider using a smaller buffer size or allocating on heap:

-    DynamicJsonDocument doc(4096);
+    DynamicJsonDocument doc(2048);  // Smaller buffer

Or validate if 4096 bytes is actually needed based on expected schedule file sizes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa12388 and b308018.

📒 Files selected for processing (3)
  • wled00/schedule.cpp (1 hunks)
  • wled00/schedule.h (1 hunks)
  • wled00/wled_server.cpp (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/schedule.h
  • wled00/wled_server.cpp
🧰 Additional context used
🧠 Learnings (1)
wled00/schedule.cpp (1)
Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.
🔇 Additional comments (3)
wled00/schedule.cpp (3)

1-11: LGTM: Clean file structure and appropriate includes.

The file header, includes, and global variable definitions are well-structured and appropriate for the scheduling functionality.


13-25: LGTM: Correct implementation for year-spanning date ranges.

The isTodayInRange function correctly handles both normal date ranges (within the same year) and year-spanning ranges (e.g., November to February). The logic properly accounts for the different comparison conditions needed for each case.


30-40: Improve anti-duplicate triggering mechanism.

The current anti-duplicate logic using thisMinute = timeinfo->tm_min + timeinfo->tm_hour * 60 has a potential issue: it resets at midnight (when the calculation wraps from 1439 back to 0), which could cause missed triggers or double triggers around midnight transitions.

Consider using a more robust approach:

-    int thisMinute = timeinfo->tm_min + timeinfo->tm_hour * 60;
+    // Use a combination of day and minute to handle midnight rollover
+    int thisDayMinute = (timeinfo->tm_yday * 1440) + (timeinfo->tm_hour * 60) + timeinfo->tm_min;

Or alternatively, store the last triggered time as a timestamp and compare against a minimum interval.

Likely an incorrect or invalid review comment.

PagedPenguin and others added 2 commits July 13, 2025 20:02
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
wled00/schedule.cpp (2)

13-25: Add comments to clarify the date range logic.

The isTodayInRange function correctly handles both normal date ranges and ranges that span year boundaries, but the logic could benefit from clearer documentation.

Consider adding comments to explain the two cases:

 bool isTodayInRange(uint8_t sm, uint8_t sd, uint8_t em, uint8_t ed, uint8_t cm, uint8_t cd)
 {
+    // Case 1: Range doesn't span year boundary (e.g., March 15 to June 20)
     if (sm < em || (sm == em && sd <= ed))
     {
         return (cm > sm || (cm == sm && cd >= sd)) &&
                (cm < em || (cm == em && cd <= ed));
     }
+    // Case 2: Range spans year boundary (e.g., November 15 to February 10)
     else
     {
         return (cm > sm || (cm == sm && cd >= sd)) ||
                (cm < em || (cm == em && cd <= ed));
     }
 }

111-117: Consider more precise day validation.

The current validation allows days 1-31 for all months, which means invalid dates like February 30th would pass validation. While this may be acceptable for the scheduling use case, consider if more precise validation is needed.

If precise date validation is desired, you could add month-specific day validation:

+        // More precise day validation
+        uint8_t maxDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+        if (sd > maxDays[sm-1] || ed > maxDays[em-1]) {
+            DEBUG_PRINTF_P(PSTR("[Schedule] Invalid day for month in event %u, skipping\n"), numScheduleEvents);
+            continue;
+        }

However, the current validation is likely sufficient for most practical scheduling scenarios.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be08a47 and 8d696f3.

📒 Files selected for processing (1)
  • wled00/schedule.cpp (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
wled00/schedule.cpp (1)
Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.
🔇 Additional comments (3)
wled00/schedule.cpp (3)

1-12: Good modular structure and clean setup.

The header includes, file path definition, and global variables are properly organized and follow good practices for embedded systems.


30-72: Excellent performance optimization and duplicate prevention.

The checkSchedule function now correctly avoids the performance issue of loading the schedule file every minute by using a static lastMinute variable for duplicate prevention. The time validation and event matching logic is well-implemented.


74-130: Well-implemented loading function with proper safety measures.

The loadSchedule function correctly addresses all the major concerns from previous reviews:

  • Uses proper JSON buffer locking for thread safety
  • Validates JSON field ranges before assignment
  • Has comprehensive error handling with proper cleanup
  • Closes files before releasing locks

The validation ranges are appropriate for the use case.

@DedeHai
Copy link
Collaborator

DedeHai commented Jul 14, 2025

thanks for contributing.
why not extend the existing scheduler?
and please remove all unnecessary changes.
The static schedule wastes a lot of RAM, if done that way, it should be a usermod.

@blazoncek
Copy link
Collaborator

Please revert all unnecessary changes and then I'll have a look.

@PagedPenguin
Copy link
Author

PagedPenguin commented Jul 14, 2025

I have made a new, clean implementation and this PR is now superseded PR #4775.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants