Summary
WLED firmware uses "RSVD" as a placeholder name for unallocated effect slots so the effect array index stays a stable identifier. WLED's own web UI filters these out, but python-wled passes them through verbatim, so they appear as real, selectable effects in the Home Assistant WLED integration's effect dropdown.
Selecting one of them in HA also produces a confusing result: the strip ends up running a different effect (see below).
Observed behavior in Home Assistant
On a stock v16.0.0 ESP8266 build (no WLED_ENABLE_GIF), my HA dropdown contains multiple RSVD entries.
Selecting any of them in HA always activates "Chase 3" (FX_MODE_TRICOLOR_CHASE, ID 54), and the dropdown then displays "Chase 3" as the current selection. This is because python-wled resolves every "RSVD" name to the same first matching effect_id (53), and the firmware then advances 53 to the next non-reserved slot, 54.
Reproduction with curl (the second case shows the same firmware skip applied to a different reserved cluster — setting any ID inside 187..201 ends up at 202):
$ curl -s http://wled/json/effects | jq 'to_entries | map(select(.value=="RSVD")) | .[0:3]'
[{"key":53,"value":"RSVD"},{"key":142,"value":"RSVD"},{"key":169,"value":"RSVD"}]
$ curl -s -X POST -H 'Content-Type: application/json' \
-d '{"seg":{"id":0,"fx":53}}' http://wled/json
{"success":true}
$ curl -s http://wled/json/state | jq '.seg[0].fx'
54
$ curl -s -X POST -H 'Content-Type: application/json' \
-d '{"seg":{"id":0,"fx":187}}' http://wled/json
{"success":true}
$ curl -s http://wled/json/state | jq '.seg[0].fx'
202
The chain producing this:
- HA sends
effect="RSVD" to python-wled's segment() setter.
python-wled resolves the name to the first matching effect_id (e.g. 53).
- WLED's
Segment::setMode explicitly skips reserved slots:
Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
// skip reserved
while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++;
if (fx >= strip.getModeCount()) fx = 0; // set solid mode
...
}
seg.mode advances to the next non-reserved slot. HA reads it back and shows that effect's name in the dropdown.
The firmware itself treats "RSVD" as something that must never be a final selection — but this is invisible to python-wled.
Where python-wled parses effects
src/wled/models.py (both Device.__pre_deserialize__ and Device.update_from_dict) enumerates the array as-is, with no filter:
if _effects := d.get("effects"):
d["effects"] = {
effect_id: {"effect_id": effect_id, "name": name}
for effect_id, name in enumerate(_effects)
}
For comparison, the WLED web UI filters at wled00/data/index.js @ v16.0.0, line 951:
if (ef.name.indexOf("RSVD") < 0) {
html += generateListItemHtml('fx', id, nm, 'setFX', '', fd);
}
"RSVD" is an established WLED-side convention (defined as _data_RESERVED in wled00/FX.cpp @ v16.0.0, line 10948) — emitted on the wire to keep array indices stable, filtered out at the presentation layer.
Where to fix this
-
WLED firmware (drop RSVD from JSON). Not feasible without an API redesign: clients derive effect_id from the array index via enumerate(), and seg.fx only accepts the integer ID (not a name). Removing entries would shift every subsequent ID and silently break presets and automations.
-
python-wled (preferred). Mirror the WLED web UI: keep enumerate() over the raw array so effect_id stays aligned with the firmware's internal IDs, but drop placeholders before they reach consumers.
-
HA wled core integration. Solves it for HA only; every other python-wled consumer keeps seeing RSVD. It also pushes WLED-domain knowledge into HA, conflicting with the platinum-tier guideline that domain logic belongs in the library.
I'm filing this here (option 2) because RSVD is a WLED-API convention, not a Home-Assistant concern.
Suggested patch
In both __pre_deserialize__ and update_from_dict:
_RESERVED_EFFECT_NAMES: frozenset[str] = frozenset({"RSVD"})
if _effects := d.get("effects"):
d["effects"] = {
effect_id: {"effect_id": effect_id, "name": name}
for effect_id, name in enumerate(_effects)
if name not in _RESERVED_EFFECT_NAMES
}
The filter must run after enumerate() so effect_id continues to match the firmware-side index. Happy to open a PR — let me know if you'd prefer a different shape (e.g. an include_reserved=True escape hatch).
Environment
- WLED firmware: v16.0.0 (stock ESP8266, no
WLED_ENABLE_GIF)
python-wled: 0.22.0
- Home Assistant Core: 2026.4.4
Summary
WLED firmware uses
"RSVD"as a placeholder name for unallocated effect slots so the effect array index stays a stable identifier. WLED's own web UI filters these out, butpython-wledpasses them through verbatim, so they appear as real, selectable effects in the Home Assistant WLED integration's effect dropdown.Selecting one of them in HA also produces a confusing result: the strip ends up running a different effect (see below).
Observed behavior in Home Assistant
On a stock
v16.0.0ESP8266 build (noWLED_ENABLE_GIF), my HA dropdown contains multipleRSVDentries.Selecting any of them in HA always activates "Chase 3" (
FX_MODE_TRICOLOR_CHASE, ID 54), and the dropdown then displays "Chase 3" as the current selection. This is becausepython-wledresolves every"RSVD"name to the same first matchingeffect_id(53), and the firmware then advances 53 to the next non-reserved slot, 54.Reproduction with
curl(the second case shows the same firmware skip applied to a different reserved cluster — setting any ID inside187..201ends up at202):The chain producing this:
effect="RSVD"topython-wled'ssegment()setter.python-wledresolves the name to the first matchingeffect_id(e.g. 53).Segment::setModeexplicitly skips reserved slots:seg.modeadvances to the next non-reserved slot. HA reads it back and shows that effect's name in the dropdown.The firmware itself treats
"RSVD"as something that must never be a final selection — but this is invisible topython-wled.Where python-wled parses effects
src/wled/models.py(bothDevice.__pre_deserialize__andDevice.update_from_dict) enumerates the array as-is, with no filter:For comparison, the WLED web UI filters at
wled00/data/index.js@ v16.0.0, line 951:"RSVD"is an established WLED-side convention (defined as_data_RESERVEDinwled00/FX.cpp@ v16.0.0, line 10948) — emitted on the wire to keep array indices stable, filtered out at the presentation layer.Where to fix this
WLED firmware (drop
RSVDfrom JSON). Not feasible without an API redesign: clients deriveeffect_idfrom the array index viaenumerate(), andseg.fxonly accepts the integer ID (not a name). Removing entries would shift every subsequent ID and silently break presets and automations.python-wled(preferred). Mirror the WLED web UI: keepenumerate()over the raw array soeffect_idstays aligned with the firmware's internal IDs, but drop placeholders before they reach consumers.HA
wledcore integration. Solves it for HA only; every otherpython-wledconsumer keeps seeingRSVD. It also pushes WLED-domain knowledge into HA, conflicting with the platinum-tier guideline that domain logic belongs in the library.I'm filing this here (option 2) because
RSVDis a WLED-API convention, not a Home-Assistant concern.Suggested patch
In both
__pre_deserialize__andupdate_from_dict:The filter must run after
enumerate()soeffect_idcontinues to match the firmware-side index. Happy to open a PR — let me know if you'd prefer a different shape (e.g. aninclude_reserved=Trueescape hatch).Environment
WLED_ENABLE_GIF)python-wled: 0.22.0