feat: expose per-attribute manual-control state (#1469)

Adds two new read-only state attributes on the AdaptiveSwitch entity:

- manual_control_brightness: list of light entity_ids whose brightness
  axis is currently in manual override (i.e. AL is paused for brightness
  on those lights).
- manual_control_color: same, for the color axis.

These mirror the existing 'manual_control' attribute (which is a
union of both axes) but expose the LightControlAttributes bitfield
that AL already tracks internally per-light.

Why: the existing 'manual_control' state attribute and the
adaptive_lighting.manual_control event are useful, but neither lets
a template or dashboard see which axis is paused without subscribing
to events. This is especially important with take_over_control_mode:
pause_changed, where one axis can be manual while the other still
adapts. Now a Lovelace card or template sensor can display
brightness/color manual state directly via state_attr().

Test: extends test_manual_control to assert the new attributes
reflect the bitfield correctly.

No new platform, no breaking changes.

Co-authored-by: Bas Nijholt <basnijholt@gmail.com>
This commit is contained in:
Jared Jensen 2026-09-06 00:59:38 -07:00 • committed by GitHub
commit e453541a78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 55 additions and 0 deletions

View file

@ -738,10 +738,19 @@ async def test_manual_control(
await turn_light(True, brightness=increased_brightness())
# Check that ENTITY_LIGHT_1 is manually controlled
assert manual_control[ENTITY_LIGHT_1] == LightControlAttributes.BRIGHTNESS
# Per-attribute state attributes should reflect this
state_attrs = hass.states.get(switch.entity_id).attributes
assert ENTITY_LIGHT_1 in state_attrs["manual_control"]
assert ENTITY_LIGHT_1 in state_attrs["manual_control_brightness"]
assert ENTITY_LIGHT_1 not in state_attrs["manual_control_color"]
# Test adaptive_lighting.set_manual_control
await change_manual_control(False)
# Check that ENTITY_LIGHT_1 is not manually controlled
assert not manual_control[ENTITY_LIGHT_1]
state_attrs = hass.states.get(switch.entity_id).attributes
assert ENTITY_LIGHT_1 not in state_attrs["manual_control"]
assert ENTITY_LIGHT_1 not in state_attrs["manual_control_brightness"]
assert ENTITY_LIGHT_1 not in state_attrs["manual_control_color"]
# Check that toggling light off to on resets manual control
await change_manual_control(True)
@ -865,6 +874,9 @@ async def test_manual_control(
assert not manual_control[ENTITY_LIGHT_1]
await change_manual_control(True)
assert manual_control[ENTITY_LIGHT_1] == LightControlAttributes.ALL
state_attrs = hass.states.get(switch.entity_id).attributes
assert state_attrs["manual_control_brightness"] == [ENTITY_LIGHT_1]
assert state_attrs["manual_control_color"] == [ENTITY_LIGHT_1]
# Check that manual control `False` unsets all attributes
await change_manual_control(False)
@ -875,6 +887,9 @@ async def test_manual_control(
assert manual_control[ENTITY_LIGHT_1] == LightControlAttributes.BRIGHTNESS
await change_manual_control("color")
assert manual_control[ENTITY_LIGHT_1] == LightControlAttributes.COLOR
state_attrs = hass.states.get(switch.entity_id).attributes
assert state_attrs["manual_control_brightness"] == []
assert state_attrs["manual_control_color"] == [ENTITY_LIGHT_1]
@flaky(max_runs=3, min_passes=1)