refactor: Update RGB Idle Light to Light Entity#63
Open
BambamNZ wants to merge 1 commit into
Open
Conversation
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Replace RGB Idle Light switch with a proper light entity (on/off + brightness)
Summary
The
rgb_idle_lightswitch faked "on/off" by slamming brightness to 100%/0%,which meant turning it on always destroyed any custom brightness you'd set,
and caused a double-toggle bug where the first tap just resynced state and
the second one actually flipped the light. This PR replaces it with a proper
lightentity that uses the device's real on/off flag, kept entirelyseparate from brightness, plus working brightness control.
Problem
There's no real on/off state here at all -
is_onis inferred frombrightness > 0, andturn_on/turn_offjust hammer brightness to 100 or0. Two visible symptoms:
"on" and "brightness = 100" were treated as the same thing.
derived
brightness > 0boolean would drift apart, so the first tap justresynced them and the second one actually worked.
Root cause (found via WebSocket sniffing against the device)
The firmware exposes real, independent state for on/off and brightness that
this integration was never using:
led.on(read) /led.on+settings.on+settings.current_mode(write, must be sent together in one message)led.brightness(read) /led.brightness(int) +settings.rgb_info_brightness(string) (write, must be sent together)Two non-obvious things turned up during testing directly against the device
with
websocat(not just capturing the web UI, since one of its capturedkeys turned out to be a red herring):
On/off must be sent as a single atomic payload including
settings.current_mode- the device will not turn off without it:{"settings":{"on":false,"current_mode":0},"led":{"on":false}}Sending
led.onandsettings.onas two separate messages (which iswhat the web UI's own network capture appeared to do) is not reliable.
Brightness needs both
led.brightness(int) andsettings.rgb_info_brightness(string) together - the settings sidedoes not propagate into
led.brightnesson its own, andled.brightnessis the field that reflects the live, current state. Writing only
settings.rgb_info_brightness(as the original switch did) changes thephysical light but leaves the reported state permanently stale:
{"led":{"brightness":84},"settings":{"rgb_info_brightness":"84"}}Also discovered along the way: the coordinator had no
update_intervalsetat all, meaning it only ever refreshed when an entity action explicitly
triggered it - immediately after sending a command. Combined with the
device using a brand-new WebSocket connection per call (see
websocket.py), an action-triggered refresh could race the device's owninternal state update and read back stale data, with nothing to
self-correct it afterwards. Adding periodic polling to fix that exposed a
second, pre-existing issue:
async_get_data/async_sendused a hard1-second timeout covering the full connect+handshake+respond cycle,
with no retry. That budget was rarely tested when connections only
happened right after a user action, but polling every 30s continuously
(~2,880 times/day) reliably caught the device's embedded WS stack on a slow
beat often enough to flap the whole integration to
unavailableseveraltimes an hour. Fixed by loosening the timeout to 5s, retrying once on a
timeout before treating it as a real failure, and backing the poll interval
off to 60s.
Changes
light.py(new) -rgb_idle_lightis now aColorMode.BRIGHTNESSlight entity:
led.on/led.brightnessdirectly instead of inferringon/off from brightness.
turn_on/turn_offsend the atomic payloads described above.with a reconciliation refresh run as a delayed background task (not
inline) so it doesn't race the device's own state update.
switch.py-PandaStatusRGBIdleSwitchremoved.PandaStatusAPSwitchis untouched.
websocket.py- timeout on bothasync_get_dataandasync_sendraised from 1s to 5s, giving the device's embedded WS stack realistic
breathing room now that polling happens continuously rather than only
right after user actions.
coordinator.py- a single timeout is now retried once before beingraised as a real
UpdateFailed, so one slow poll doesn't flap entities tounavailable.
__init__.pyPlatform.LIGHTadded toPLATFORMS.update_interval=timedelta(seconds=60), so stateself-corrects periodically regardless of what individual actions do,
without hammering the device's WS stack too aggressively.
Breaking change
switch.<device>_rgb_idle_lightis removed and replaced bylight.<device>_rgb_idle_light. Any automations, scripts, or dashboardsreferencing the old switch entity will need to be updated to point at the
new light entity. Given this, it's probably worth a
3.0.0version bumprather than a patch/minor release.
Testing
Manually tested against a live device (BTT Panda Status on a Bambu A1 mini)
over several days:
the displayed value correctly.
of resetting it.
correctly.
no further
unavailableflapping observed (was happening several timesan hour before that fix).
Not yet covered: behaviour under rapid repeated brightness changes (e.g.
fast slider dragging), which may still exhibit minor last-write-wins
ordering issues given the device's one-connection-per-command design - this
appears to be a protocol-level limitation rather than something fixable
purely on the entity side, and could be revisited with command debouncing
if it turns out to matter in practice.