-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Add boot_lor usermod - enforces a configured realtime override at startup #5569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+337
−0
Closed
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ce0ca4e
Add boot_lor usermod
smitty078 d58db84
Applied all actionable coderabbitai changes
smitty078 a8c4dce
Removed waitForUDP option as it turns out it does not work with DDP t…
smitty078 e481012
Yet more coderabbit suggestions
smitty078 cd65893
Clarifies behavior in readme. Maybe the last coderabbit?
smitty078 9d3e2da
Clarify network activity wait condition in readme
smitty078 f6759a6
Update note on realtime streaming behavior
smitty078 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "name": "boot_lor", | ||
| "version": "1.0.0", | ||
| "description": "Set WLED realtime override at boot", | ||
| "frameworks": "arduino", | ||
| "platforms": "espressif32", | ||
| "build": { | ||
| "libArchive": false, | ||
| "srcDir": "." | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # Usermods API v2 boot_lor usermod | ||
|
|
||
| ## Installation | ||
|
|
||
| Add `boot_lor` to `custom_usermods` in your PlatformIO environment and compile! | ||
|
|
||
| ## Overview | ||
|
|
||
| `boot_lor` is a usermod that sets the WLED realtime override mode (`lor`) at boot. | ||
|
|
||
| It is designed for setups where WLED is primarily controlled via external APIs (e.g. HomeKit, Home Assistant, or custom integrations), and realtime streaming protocols (such as DDP) are only used occasionally. | ||
|
|
||
| By default, WLED enables realtime streaming at boot when data is received. This can interfere with API-driven control flows. This usermod ensures that a desired `lor` mode (typically `lor:2`) is enforced during startup. | ||
|
|
||
| --- | ||
|
|
||
| ## Use Case | ||
|
|
||
| This usermod is intended for environments where: | ||
|
|
||
| - WLED is primarily controlled via an external system (e.g. HomeKit via Homebridge, Home Assistant, or direct API usage) | ||
| - Multiple controllers should behave as **independent devices** under normal operation | ||
| - Realtime streaming (DDP, E1.31, etc.) is used **only when explicitly enabled** | ||
|
|
||
| ### Example scenario | ||
|
|
||
| - Two WLED controllers are used as separate lights in HomeKit | ||
| - A secondary setup uses WLED "virtual LEDs" to mirror one controller to another via DDP | ||
| - This works well for effects and synchronized control | ||
|
|
||
| **Problem:** | ||
| - At boot, realtime communication may take control as soon as packets arrive, overriding API-based control unexpectedly. | ||
| - Using JSON or HTTP API in a boot preset does not relaibly set lor | ||
|
|
||
| **Solution:** | ||
| Set `lor:2` at boot to disable realtime takeover by default. | ||
| When realtime control is desired, manually switch back to `lor:0`. | ||
|
|
||
| --- | ||
|
|
||
| ## Behavior | ||
|
|
||
| The usermod applies the configured `lor` value during startup using the following sequence: | ||
|
|
||
| ### Default behavior | ||
|
|
||
| ``` | ||
| WiFi connected → first UDP packet received → (optional delay) → apply lor → assert for N seconds → stop | ||
| ``` | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| - Waits for network connectivity | ||
| - Optionally waits for the first UDP packet (recommended for DDP setups) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| - Waits an additional configurable delay (if set) | ||
| - Applies the configured `lor` value | ||
| - Reasserts it for a short period to allow system "settling" | ||
| - Stops running after completion | ||
|
|
||
| --- | ||
|
|
||
| ## Configuration | ||
|
|
||
| The following options are available under `"boot_lor"` in the WLED config: | ||
|
|
||
| ```json | ||
| "boot_lor": { | ||
| "bootLor": 2, | ||
| "waitForUdpPacket": true, | ||
| "additionalWaitSec": 0, | ||
| "assertForSec": 10 | ||
| } | ||
| ``` | ||
|
|
||
| ### Options | ||
|
|
||
| | Name | Type | Default | Description | | ||
| |---------------------|------|---------|-------------| | ||
| | `bootLor` | int | `2` | Realtime override mode to apply. Valid values: `-1` (disabled), `0`, `1`, `2` | | ||
| | `waitForUdpPacket` | bool | `true` | Wait for first UDP packet before starting the delay timer | | ||
| | `additionalWaitSec` | int | `0` | Additional delay (in seconds) after trigger (connection or UDP) before applying | | ||
| | `assertForSec` | int | `10` | Duration (in seconds) to reassert the value after first application | | ||
|
|
||
| --- | ||
|
|
||
| ## Recommended Settings | ||
|
|
||
| For most DDP / API-first setups: | ||
|
|
||
| ```json | ||
| { | ||
| "bootLor": 2, | ||
| "waitForUdpPacket": true, | ||
| "additionalWaitSec": 0, | ||
| "assertForSec": 10 | ||
| } | ||
| ``` | ||
|
|
||
| This ensures: | ||
|
|
||
| - Realtime streaming does not take control unexpectedly at boot | ||
| - The system waits for actual network activity before acting | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| - The setting is reinforced briefly to avoid race conditions | ||
|
|
||
| --- | ||
|
|
||
| ## Notes | ||
|
|
||
| - This usermod does **not** block or modify UDP traffic | ||
| - It does **not** interfere with realtime streaming once `lor` is manually changed | ||
| - It simply ensures a predictable startup state | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| --- | ||
|
|
||
| ## Status | ||
|
|
||
| After completion, the usermod stops running and has no ongoing impact on system performance. | ||
|
|
||
| --- | ||
|
|
||
| ## JSON Info | ||
|
|
||
| The current state is exposed under `info -> u`: | ||
|
|
||
| ``` | ||
| Boot LOR: [bootLor, state, realtimeOverride] | ||
| ``` | ||
|
|
||
| Where: | ||
| - `state` is one of `waiting`, `applied`, or `finished` | ||
|
|
||
| --- | ||
|
|
||
| ## Tested | ||
| - Build: esp32dev | ||
| - Runtime: tested on ESP32-D0WD-V3 | ||
|
|
||
| --- | ||
|
|
||
| ## Summary | ||
|
|
||
| This usermod provides a simple and reliable way to: | ||
|
|
||
| - Default to API-based control at boot | ||
| - Avoid unintended realtime takeover | ||
| - Retain full flexibility to enable realtime modes when desired | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| #include "wled.h" | ||
|
|
||
| class BootLorUsermod : public Usermod { | ||
| private: | ||
| static constexpr const char* _name = "boot_lor"; | ||
|
|
||
| int8_t bootLor = 2; // -1 disabled, 0/1/2 valid lor values | ||
| bool waitForUdpPacket = true; // wait for first UDP packet before starting delay | ||
| uint8_t additionalWaitSec = 0; // seconds after connection or UDP packet | ||
| uint16_t assertForSec = 10; // reassert window after first apply | ||
|
|
||
| unsigned long referenceMs = 0; | ||
| unsigned long firstAppliedMs = 0; | ||
|
|
||
| bool referenceSet = false; | ||
| bool applied = false; | ||
| bool finished = false; | ||
|
|
||
| bool isValidLor(int8_t value) const { | ||
| return value >= -1 && value <= 2; | ||
| } | ||
|
|
||
| bool isEnabled() const { | ||
| return isValidLor(bootLor) && bootLor >= 0; | ||
| } | ||
|
|
||
| void setReferenceTime(unsigned long now) { | ||
| referenceMs = now; | ||
| referenceSet = true; | ||
| } | ||
|
|
||
| bool additionalWaitElapsed() const { | ||
| const unsigned long waitMs = (unsigned long)additionalWaitSec * 1000UL; | ||
| return millis() - referenceMs >= waitMs; | ||
| } | ||
|
|
||
| bool readyToApply() const { | ||
| if (!isEnabled() || finished || !referenceSet) return false; | ||
| if (!WLED_CONNECTED) return false; | ||
|
|
||
| return additionalWaitElapsed(); | ||
| } | ||
|
|
||
| void applyBootLor() { | ||
| if (realtimeOverride != bootLor) { | ||
| realtimeOverride = bootLor; | ||
| } | ||
|
|
||
| if (!applied) { | ||
| applied = true; | ||
| firstAppliedMs = millis(); | ||
| } | ||
| } | ||
|
|
||
| void runIfReady() { | ||
| if (!readyToApply()) return; | ||
|
|
||
| applyBootLor(); | ||
|
|
||
| if (millis() - firstAppliedMs > (unsigned long)assertForSec * 1000UL) { | ||
| finished = true; | ||
| } | ||
| } | ||
|
|
||
| public: | ||
| void setup() override { | ||
| } | ||
|
|
||
| void connected() override { | ||
| if (!waitForUdpPacket && !referenceSet) { | ||
| setReferenceTime(millis()); | ||
| } | ||
| } | ||
|
|
||
| void loop() override { | ||
| runIfReady(); | ||
| } | ||
|
|
||
| bool onUdpPacket(uint8_t* payload, size_t len) override { | ||
| if (waitForUdpPacket && !referenceSet && WLED_CONNECTED) { | ||
| setReferenceTime(millis()); | ||
| } | ||
|
|
||
| runIfReady(); | ||
| return false; | ||
| } | ||
|
|
||
| void addToConfig(JsonObject& root) override { | ||
| JsonObject top = root[_name]; | ||
| if (top.isNull()) top = root.createNestedObject(_name); | ||
|
|
||
| top["bootLor"] = bootLor; | ||
| top["waitForUdpPacket"] = waitForUdpPacket; | ||
| top["additionalWaitSec"] = additionalWaitSec; | ||
| top["assertForSec"] = assertForSec; | ||
| } | ||
|
|
||
| bool readFromConfig(JsonObject& root) override { | ||
| JsonObject top = root[_name]; | ||
| if (top.isNull()) return false; | ||
|
|
||
| int8_t newBootLor = top["bootLor"] | bootLor; | ||
| if (isValidLor(newBootLor)) bootLor = newBootLor; | ||
|
|
||
| waitForUdpPacket = top["waitForUdpPacket"] | waitForUdpPacket; | ||
| additionalWaitSec = top["additionalWaitSec"] | additionalWaitSec; | ||
| assertForSec = top["assertForSec"] | assertForSec; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| void addToJsonInfo(JsonObject& root) override { | ||
| JsonObject user = root["u"]; | ||
| if (user.isNull()) user = root.createNestedObject("u"); | ||
|
|
||
| JsonArray infoArr = user.createNestedArray("Boot LOR"); | ||
| infoArr.add(bootLor); | ||
| infoArr.add(finished ? "finished" : applied ? "applied" : "waiting"); | ||
| infoArr.add(realtimeOverride); | ||
| } | ||
|
|
||
| uint16_t getId() override { | ||
| return USERMOD_ID_RESERVED; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| }; | ||
|
|
||
| static BootLorUsermod boot_lor_usermod; | ||
| REGISTER_USERMOD(boot_lor_usermod); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.