From ce0ca4e3b2ad3f16d5397f6227df47a26387c835 Mon Sep 17 00:00:00 2001 From: smitty078 Date: Mon, 4 May 2026 19:10:39 -0400 Subject: [PATCH 1/7] Add boot_lor usermod Applies a configured realtime override (lor) value after initial network or UDP activity. Useful for API-driven setups (HomeKit, Home Assistant, etc.) where realtime streaming (DDP/E1.31) should not take control at boot. - Optional wait for first UDP packet - Optional additional delay before applying - Reasserts value for a configurable duration to handle startup races --- usermods/boot_lor/library.json | 11 ++ usermods/boot_lor/readme.md | 144 +++++++++++++++++++++++++ usermods/boot_lor/usermod_boot_lor.cpp | 128 ++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 usermods/boot_lor/library.json create mode 100644 usermods/boot_lor/readme.md create mode 100644 usermods/boot_lor/usermod_boot_lor.cpp diff --git a/usermods/boot_lor/library.json b/usermods/boot_lor/library.json new file mode 100644 index 0000000000..396e406459 --- /dev/null +++ b/usermods/boot_lor/library.json @@ -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": "." + } +} diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md new file mode 100644 index 0000000000..42055155e6 --- /dev/null +++ b/usermods/boot_lor/readme.md @@ -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 +``` + +- Waits for network connectivity +- Optionally waits for the first UDP packet (recommended for DDP setups) +- 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 +- 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 + +--- + +## 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 diff --git a/usermods/boot_lor/usermod_boot_lor.cpp b/usermods/boot_lor/usermod_boot_lor.cpp new file mode 100644 index 0000000000..ca66145049 --- /dev/null +++ b/usermods/boot_lor/usermod_boot_lor.cpp @@ -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; + } +}; + +static BootLorUsermod boot_lor_usermod; +REGISTER_USERMOD(boot_lor_usermod); From d58db84e13a7e675b786637dbd0eeb301c037ff2 Mon Sep 17 00:00:00 2001 From: smitty078 Date: Mon, 4 May 2026 21:46:46 -0400 Subject: [PATCH 2/7] Applied all actionable coderabbitai changes --- usermods/boot_lor/readme.md | 6 +- usermods/boot_lor/usermod_boot_lor.cpp | 220 ++++++++++++------------- wled00/const.h | 1 + 3 files changed, 114 insertions(+), 113 deletions(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index 42055155e6..410017de9b 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -30,7 +30,7 @@ This usermod is intended for environments where: **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 +- Using JSON or HTTP API in a boot preset does not reliably set lor **Solution:** Set `lor:2` at boot to disable realtime takeover by default. @@ -44,9 +44,9 @@ The usermod applies the configured `lor` value during startup using the followin ### Default behavior -``` + WiFi connected → first UDP packet received → (optional delay) → apply lor → assert for N seconds → stop -``` + - Waits for network connectivity - Optionally waits for the first UDP packet (recommended for DDP setups) diff --git a/usermods/boot_lor/usermod_boot_lor.cpp b/usermods/boot_lor/usermod_boot_lor.cpp index ca66145049..3e62638318 100644 --- a/usermods/boot_lor/usermod_boot_lor.cpp +++ b/usermods/boot_lor/usermod_boot_lor.cpp @@ -2,126 +2,126 @@ 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; + 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; } - void setReferenceTime(unsigned long now) { - referenceMs = now; - referenceSet = true; + if (!applied) { + applied = true; + firstAppliedMs = millis(); } + } + + void runIfReady() { + if (!readyToApply()) return; - 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(); - } + applyBootLor(); - void applyBootLor() { - if (realtimeOverride != bootLor) { - realtimeOverride = bootLor; - } - - if (!applied) { - applied = true; - firstAppliedMs = millis(); - } + if (millis() - firstAppliedMs > (unsigned long)assertForSec * 1000UL) { + finished = true; } - - void runIfReady() { - if (!readyToApply()) return; - - applyBootLor(); - - if (millis() - firstAppliedMs > (unsigned long)assertForSec * 1000UL) { - finished = true; - } - } - + } + public: - void setup() override { + void setup() override { + } + + void connected() override { + if (!waitForUdpPacket && !referenceSet) { + setReferenceTime(millis()); } - - 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 loop() override { + runIfReady(); + } + + bool onUdpPacket(uint8_t* payload, size_t len) override { + if (waitForUdpPacket && !referenceSet && WLED_CONNECTED) { + setReferenceTime(millis()); } - 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; - } + 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_BOOT_LOR; + } }; static BootLorUsermod boot_lor_usermod; diff --git a/wled00/const.h b/wled00/const.h index 62d9c45f4d..cd15abbf4a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -225,6 +225,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h" #define USERMOD_ID_BRIGHTNESS_FOLLOW_SUN 57 //Usermod "usermod_v2_brightness_follow_sun.h" #define USERMOD_ID_USER_FX 58 //Usermod "user_fx" +#define USERMOD_ID_BOOT_LOR 59 //Usermod "boot_lor" //Wifi encryption type #ifdef WLED_ENABLE_WPA_ENTERPRISE From a8c4dce0c1f29465d52204366cf739dfd1ec1c57 Mon Sep 17 00:00:00 2001 From: smitty078 Date: Mon, 4 May 2026 23:44:05 -0400 Subject: [PATCH 3/7] Removed waitForUDP option as it turns out it does not work with DDP traffic. Additional changes suggested by coderabbit including docstrings --- usermods/boot_lor/readme.md | 8 +- usermods/boot_lor/usermod_boot_lor.cpp | 113 +++++++++++++++++++------ 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index 410017de9b..5ed620a0b9 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -44,9 +44,9 @@ The usermod applies the configured `lor` value during startup using the followin ### Default behavior - -WiFi connected → first UDP packet received → (optional delay) → apply lor → assert for N seconds → stop - +```text +WiFi connected → (optional delay) → apply lor → assert for N seconds → stop +``` - Waits for network connectivity - Optionally waits for the first UDP packet (recommended for DDP setups) @@ -120,7 +120,7 @@ After completion, the usermod stops running and has no ongoing impact on system The current state is exposed under `info -> u`: -``` +```text Boot LOR: [bootLor, state, realtimeOverride] ``` diff --git a/usermods/boot_lor/usermod_boot_lor.cpp b/usermods/boot_lor/usermod_boot_lor.cpp index 3e62638318..5090297f21 100644 --- a/usermods/boot_lor/usermod_boot_lor.cpp +++ b/usermods/boot_lor/usermod_boot_lor.cpp @@ -1,46 +1,81 @@ #include "wled.h" +/** + * @brief Applies a configured WLED realtime override mode during startup. + * + * The usermod waits for network connectivity, waits an optional additional + * delay, then applies the configured realtime override value and reasserts it + * for a short settling period. + */ class BootLorUsermod : public Usermod { private: - static constexpr const char* _name = "boot_lor"; + static constexpr const char* _name = "boot_lor"; ///< JSON configuration key for this usermod. - 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 + int8_t bootLor = 2; ///< Realtime override mode to apply; -1 disables the usermod. + uint8_t additionalWaitSec = 0; ///< Additional delay, in seconds, after network connection. + uint16_t assertForSec = 10; ///< Duration, in seconds, to reassert the configured override. - unsigned long referenceMs = 0; - unsigned long firstAppliedMs = 0; + unsigned long connectedMs = 0; ///< Timestamp when network connectivity became available. + unsigned long firstAppliedMs = 0; ///< Timestamp of the first successful realtime override application. - bool referenceSet = false; - bool applied = false; - bool finished = false; + bool connectedSeen = false; ///< True once the network connection timestamp has been captured. + bool applied = false; ///< True once the configured realtime override has been applied. + bool finished = false; ///< True once the assertion window has completed. + /** + * @brief Checks whether a realtime override value is valid. + * + * @param value Realtime override value to validate. + * @return true if the value is -1, 0, 1, or 2. + */ bool isValidLor(int8_t value) const { return value >= -1 && value <= 2; } + /** + * @brief Checks whether this usermod should run. + * + * @return true when the configured realtime override is valid and not disabled. + */ bool isEnabled() const { return isValidLor(bootLor) && bootLor >= 0; } - void setReferenceTime(unsigned long now) { - referenceMs = now; - referenceSet = true; + /** + * @brief Stores the timestamp used to start the post-connection wait period. + * + * @param now Current millis() timestamp. + */ + void setConnectedTime(unsigned long now) { + connectedMs = now; + connectedSeen = true; } + /** + * @brief Checks whether the configured post-connection wait period has elapsed. + * + * @return true once enough time has passed since network connection. + */ bool additionalWaitElapsed() const { const unsigned long waitMs = (unsigned long)additionalWaitSec * 1000UL; - return millis() - referenceMs >= waitMs; + return millis() - connectedMs >= waitMs; } + /** + * @brief Checks whether all conditions are met to apply the realtime override. + * + * @return true when the usermod is enabled, connected, and past its wait period. + */ bool readyToApply() const { - if (!isEnabled() || finished || !referenceSet) return false; + if (!isEnabled() || finished || !connectedSeen) return false; if (!WLED_CONNECTED) return false; return additionalWaitElapsed(); } + /** + * @brief Applies the configured realtime override and records first application time. + */ void applyBootLor() { if (realtimeOverride != bootLor) { realtimeOverride = bootLor; @@ -52,6 +87,9 @@ class BootLorUsermod : public Usermod { } } + /** + * @brief Applies and reasserts the configured realtime override when ready. + */ void runIfReady() { if (!readyToApply()) return; @@ -63,38 +101,48 @@ class BootLorUsermod : public Usermod { } public: + /** + * @brief Initializes the usermod. + */ void setup() override { } + /** + * @brief Starts the wait timer when networking becomes available. + */ void connected() override { - if (!waitForUdpPacket && !referenceSet) { - setReferenceTime(millis()); + if (!connectedSeen) { + setConnectedTime(millis()); } } + /** + * @brief Runs the non-blocking assertion state machine. + */ void loop() override { runIfReady(); } - bool onUdpPacket(uint8_t* payload, size_t len) override { - if (waitForUdpPacket && !referenceSet && WLED_CONNECTED) { - setReferenceTime(millis()); - } - - runIfReady(); - return false; - } - + /** + * @brief Adds this usermod's settings to the WLED configuration JSON. + * + * @param root Root configuration JSON object. + */ 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; } + /** + * @brief Reads this usermod's settings from the WLED configuration JSON. + * + * @param root Root configuration JSON object. + * @return true if this usermod's configuration object exists. + */ bool readFromConfig(JsonObject& root) override { JsonObject top = root[_name]; if (top.isNull()) return false; @@ -102,13 +150,17 @@ class BootLorUsermod : public Usermod { 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; } + /** + * @brief Adds runtime status information to the WLED info JSON. + * + * @param root Root info JSON object. + */ void addToJsonInfo(JsonObject& root) override { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -119,6 +171,11 @@ class BootLorUsermod : public Usermod { infoArr.add(realtimeOverride); } + /** + * @brief Returns the registered usermod identifier. + * + * @return USERMOD_ID_BOOT_LOR. + */ uint16_t getId() override { return USERMOD_ID_BOOT_LOR; } From e481012aa70638a31fd0ddff8e3cea1ab427584c Mon Sep 17 00:00:00 2001 From: smitty078 Date: Mon, 4 May 2026 23:54:21 -0400 Subject: [PATCH 4/7] Yet more coderabbit suggestions --- usermods/boot_lor/readme.md | 7 +----- usermods/boot_lor/usermod_boot_lor.cpp | 33 +++++++++++++------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index 5ed620a0b9..e4f1c5bfed 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -49,7 +49,6 @@ WiFi connected → (optional delay) → apply lor → assert for N seconds → s ``` - Waits for network connectivity -- Optionally waits for the first UDP packet (recommended for DDP setups) - Waits an additional configurable delay (if set) - Applies the configured `lor` value - Reasserts it for a short period to allow system "settling" @@ -64,7 +63,6 @@ The following options are available under `"boot_lor"` in the WLED config: ```json "boot_lor": { "bootLor": 2, - "waitForUdpPacket": true, "additionalWaitSec": 0, "assertForSec": 10 } @@ -75,8 +73,7 @@ The following options are available under `"boot_lor"` in the WLED config: | 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 | +| `additionalWaitSec` | int | `0` | Additional delay (in seconds) after trigger (connection) before applying | | `assertForSec` | int | `10` | Duration (in seconds) to reassert the value after first application | --- @@ -88,7 +85,6 @@ For most DDP / API-first setups: ```json { "bootLor": 2, - "waitForUdpPacket": true, "additionalWaitSec": 0, "assertForSec": 10 } @@ -104,7 +100,6 @@ This ensures: ## 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 diff --git a/usermods/boot_lor/usermod_boot_lor.cpp b/usermods/boot_lor/usermod_boot_lor.cpp index 5090297f21..cfb2a47065 100644 --- a/usermods/boot_lor/usermod_boot_lor.cpp +++ b/usermods/boot_lor/usermod_boot_lor.cpp @@ -9,7 +9,7 @@ */ class BootLorUsermod : public Usermod { private: - static constexpr const char* _name = "boot_lor"; ///< JSON configuration key for this usermod. + static const char _name[] PROGMEM; ///< JSON configuration key for this usermod. int8_t bootLor = 2; ///< Realtime override mode to apply; -1 disables the usermod. uint8_t additionalWaitSec = 0; ///< Additional delay, in seconds, after network connection. @@ -104,8 +104,7 @@ class BootLorUsermod : public Usermod { /** * @brief Initializes the usermod. */ - void setup() override { - } + void setup() override {} /** * @brief Starts the wait timer when networking becomes available. @@ -129,12 +128,12 @@ class BootLorUsermod : public Usermod { * @param root Root configuration JSON object. */ void addToConfig(JsonObject& root) override { - JsonObject top = root[_name]; - if (top.isNull()) top = root.createNestedObject(_name); + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) top = root.createNestedObject(FPSTR(_name)); - top["bootLor"] = bootLor; - top["additionalWaitSec"] = additionalWaitSec; - top["assertForSec"] = assertForSec; + top[F("bootLor")] = bootLor; + top[F("additionalWaitSec")] = additionalWaitSec; + top[F("assertForSec")] = assertForSec; } /** @@ -144,14 +143,14 @@ class BootLorUsermod : public Usermod { * @return true if this usermod's configuration object exists. */ bool readFromConfig(JsonObject& root) override { - JsonObject top = root[_name]; + JsonObject top = root[FPSTR(_name)]; if (top.isNull()) return false; - int8_t newBootLor = top["bootLor"] | bootLor; + int8_t newBootLor = top[F("bootLor")] | bootLor; if (isValidLor(newBootLor)) bootLor = newBootLor; - additionalWaitSec = top["additionalWaitSec"] | additionalWaitSec; - assertForSec = top["assertForSec"] | assertForSec; + additionalWaitSec = top[F("additionalWaitSec")] | additionalWaitSec; + assertForSec = top[F("assertForSec")] | assertForSec; return true; } @@ -162,12 +161,12 @@ class BootLorUsermod : public Usermod { * @param root Root info JSON object. */ void addToJsonInfo(JsonObject& root) override { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); + JsonObject user = root[F("u")]; + if (user.isNull()) user = root.createNestedObject(F("u")); - JsonArray infoArr = user.createNestedArray("Boot LOR"); + JsonArray infoArr = user.createNestedArray(F("Boot LOR")); infoArr.add(bootLor); - infoArr.add(finished ? "finished" : applied ? "applied" : "waiting"); + infoArr.add(finished ? F("finished") : applied ? F("applied") : F("waiting")); infoArr.add(realtimeOverride); } @@ -181,5 +180,7 @@ class BootLorUsermod : public Usermod { } }; +const char BootLorUsermod::_name[] PROGMEM = "boot_lor"; + static BootLorUsermod boot_lor_usermod; REGISTER_USERMOD(boot_lor_usermod); From cd65893e9c3cac3726dc80cc21c00f3773a121df Mon Sep 17 00:00:00 2001 From: smitty078 Date: Tue, 5 May 2026 00:18:34 -0400 Subject: [PATCH 5/7] Clarifies behavior in readme. Maybe the last coderabbit? --- usermods/boot_lor/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index e4f1c5bfed..4da5cb573d 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -100,7 +100,7 @@ This ensures: ## Notes -- It does **not** interfere with realtime streaming once `lor` is manually changed +- It does **not** interfere with realtime streaming once the assertion period is over (default 10 seconds after network) - It simply ensures a predictable startup state --- From 9d3e2da4224a891992ce4f76a8630aee25e3a9c0 Mon Sep 17 00:00:00 2001 From: redpandadev <304245+smitty078@users.noreply.github.com> Date: Tue, 5 May 2026 14:03:42 -0400 Subject: [PATCH 6/7] Clarify network activity wait condition in readme --- usermods/boot_lor/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index 4da5cb573d..131628a99b 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -93,7 +93,7 @@ For most DDP / API-first setups: This ensures: - Realtime streaming does not take control unexpectedly at boot -- The system waits for actual network activity before acting +- The system waits for network connectivity before acting - The setting is reinforced briefly to avoid race conditions --- From f6759a6dacdb2691ec07b9e5ee1f18f3c430c7f1 Mon Sep 17 00:00:00 2001 From: redpandadev <304245+smitty078@users.noreply.github.com> Date: Tue, 5 May 2026 14:19:04 -0400 Subject: [PATCH 7/7] Update note on realtime streaming behavior Clarified note about realtime streaming in readme. --- usermods/boot_lor/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/boot_lor/readme.md b/usermods/boot_lor/readme.md index 131628a99b..7845e6bd9b 100644 --- a/usermods/boot_lor/readme.md +++ b/usermods/boot_lor/readme.md @@ -100,7 +100,7 @@ This ensures: ## Notes -- It does **not** interfere with realtime streaming once the assertion period is over (default 10 seconds after network) +- It does **not** interfere with realtime streaming once the assertion period is over (see startup sequence) - It simply ensures a predictable startup state ---