Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions usermods/boot_lor/library.json
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": "."
}
}
144 changes: 144 additions & 0 deletions usermods/boot_lor/readme.md
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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

**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
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

- Waits for network connectivity
- Optionally waits for the first UDP packet (recommended for DDP setups)
Comment thread
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
Comment thread
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
Comment thread
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
128 changes: 128 additions & 0 deletions usermods/boot_lor/usermod_boot_lor.cpp
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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
};

static BootLorUsermod boot_lor_usermod;
REGISTER_USERMOD(boot_lor_usermod);