feat: optimistic light state for instant UI feedback - #684
feat: optimistic light state for instant UI feedback#684wolfgang-steinberg wants to merge 2 commits into
Conversation
turn_on/off and brightness are reflected in Home Assistant immediately instead of only after the ~2 s hub poll that follows each command. The optimistic value is held until the follow-up refresh returns real hub data and is always cleared afterwards (try/finally), so a failed command self-corrects on the next refresh and there is no stale state or flicker.
The optimistic override was cleared unconditionally after each command's fixed 2s refresh. With overlapping commands (rapid on/off or dim changes) an earlier command's cleanup wiped the optimistic value a later command had just set, so the UI briefly reverted to the stale hub value before settling ~2-3s later. Guard the cleanup with a per-entity generation counter so only the latest command clears the optimistic state, and additionally drop the on/off override from _handle_coordinator_update once the hub confirms the commanded state (clearing on a match never changes what is shown, so a slow hub can no longer revert the UI). Brightness keeps the timed cleanup, now generation-guarded. Adds tests/test_light_optimistic_state.py.
|
Pushed a stability fix ( Fixed at the root with (a) a per-entity generation counter, so only the latest command clears the override, and (b) dropping the on/off override from |
|
@LGO44 Do you have lights? Can u test? |
|
I will try it next week |
Problem
After turning a Wiser light on/off (or dimming it), Home Assistant only updates
the entity state ~2–3 seconds later.
async_turn_on/async_turn_offsendthe command and then call
async_force_update(2), which deliberately waits 2 sbefore re-polling the hub. The lamp reacts instantly, but the UI lags behind
that whole delay, which feels unresponsive.
Change
Reflect the just-sent command in the entity state immediately, then confirm
it against real hub data:
_optimistic_is_on/_optimistic_percentage)after the command is sent and
async_write_ha_state()at once, so the UIupdates without waiting for the poll;
is_on/brightnessreturn the override while it is set.Clearing the override robustly (this is what keeps rapid toggles from
misbehaving — see the note below):
delayed
try/finallycleanup only clears the override if it is still thelatest command. So when commands overlap (fast toggling / dragging a dim
slider), an earlier command's cleanup can no longer wipe the value a newer
command just set;
_handle_coordinator_updateadditionally drops the on/off override assoon as the hub reports the commanded state. Clearing on a match never changes
what is shown, so a slow hub can't revert the UI to the pre-command value
between the command and its confirmation. Brightness keeps the (now
generation-guarded) timed cleanup, because the hub may legitimately report a
remapped percentage that never equals the set value exactly.
Only
light.pyis touched. Behaviour is unchanged when no command is pending(override is
None), and physical changes (wall switch, app) still come throughthe normal coordinator poll.
Why the extra guards
The first version cleared the override unconditionally after each command's
2 s refresh. With overlapping commands, the earlier command's cleanup fired
mid-flight and wiped the newer command's optimistic value, so the UI briefly
snapped back to the stale hub state before settling ~2–3 s later. Reported
during testing on rapid on/off and rapid dim changes; the two guards above fix
it at the root.
Testing
tests/test_light_optimistic_state.py: a superseded command does not clobbera newer command's override (generation guard); the on/off override is dropped
only once the hub confirms the state; a single command still clears normally.
Full
unittestsuite green.instead of ~2–3 s. Firing overlapping on/off and dim commands (fired
concurrently inside the 2 s window) and sampling the state every 0.25 s, the
entity now tracks the last command and holds it — no revert to the previous
value across the 2 s marks — then settles on the real hub value seamlessly.
Independent of #683 (multi-gang fix): that PR changes
__init__,_handle_coordinator_update,nameandunique_id; this one changes__init__,is_on,async_turn_on/off,brightnessand (now)_handle_coordinator_update. The overlaps are clean insertions that mergewithout conflict.