fix: publish manual-control state when it changes (#1538)

This commit is contained in:
Bas Nijholt 2026-09-06 12:32:40 +02:00 committed by GitHub
commit ecf2403422
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 0 deletions

View file

@ -2319,6 +2319,18 @@ class AdaptiveLightingManager:
)
self._handle_timer(light, self.auto_reset_manual_control_timers, delay, reset)
self._schedule_manual_control_state_update(light)
def _schedule_manual_control_state_update(self, *lights: str) -> None:
"""Publish shared manual-control state on every affected switch."""
# State publication must not expand groups or change tracked lights.
for entry in self.hass.config_entries.async_entries(DOMAIN):
entry_data = self.hass.data[DOMAIN].get(entry.entry_id)
if entry_data is None:
continue
switch = entry_data.get(SWITCH_DOMAIN)
if switch is not None and set(lights).intersection(switch.lights):
switch.async_schedule_update_ha_state()
def add_manual_control_attributes(
self,
@ -2419,6 +2431,8 @@ class AdaptiveLightingManager:
self.our_last_state_on_change.pop(light, None)
self.last_service_data.pop(light, None)
self.cancel_ongoing_adaptation_calls(light)
if reset_manual_control:
self._schedule_manual_control_state_update(*lights)
def _get_entity_list(self, service_data: ServiceData) -> list[str]:
if ATTR_ENTITY_ID in service_data:

View file

@ -1092,6 +1092,117 @@ async def test_mixed_turn_on_restarts_manual_control_timeout(
)
@pytest.mark.parametrize("intercept", [True, False])
@pytest.mark.parametrize(
("service_data", "brightness", "color"),
[
({ATTR_BRIGHTNESS: 200}, True, False),
({"brightness_step": 5}, True, False),
({ATTR_COLOR_TEMP_KELVIN: 4000}, False, True),
({ATTR_BRIGHTNESS: 200, ATTR_COLOR_TEMP_KELVIN: 4000}, True, True),
],
)
async def test_manual_control_state_updates_without_adaptation(
hass,
intercept,
service_data,
brightness,
color,
):
"""Publish manual state on service changes and resets, without an interval tick."""
switch, (light, *_) = await setup_lights_and_switch(
hass,
{
CONF_INTERCEPT: intercept,
CONF_MIN_BRIGHTNESS: 50,
CONF_MAX_BRIGHTNESS: 50,
},
)
events = []
hass.bus.async_listen(f"{DOMAIN}.manual_control", events.append)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: light.entity_id, **service_data},
blocking=True,
context=Context(),
)
await hass.async_block_till_done()
attrs = hass.states.get(switch.entity_id).attributes
assert attrs["manual_control"] == [light.entity_id]
assert attrs["manual_control_brightness"] == (
[light.entity_id] if brightness else []
)
assert attrs["manual_control_color"] == ([light.entity_id] if color else [])
assert len(events) == 1
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: light.entity_id},
blocking=True,
)
await hass.async_block_till_done()
attrs = hass.states.get(switch.entity_id).attributes
assert attrs["manual_control"] == []
assert attrs["manual_control_brightness"] == []
assert attrs["manual_control_color"] == []
async def test_manual_control_state_updates_shared_switches(hass):
"""Publish shared state on both profiles when one receives a service call."""
switch, (light, *_) = await setup_lights_and_switch(hass)
_, other = await setup_switch(
hass,
{CONF_NAME: "other", CONF_LIGHTS: [light.entity_id]},
)
await hass.async_block_till_done()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_MANUAL_CONTROL,
{
ATTR_ENTITY_ID: switch.entity_id,
CONF_LIGHTS: [light.entity_id],
CONF_MANUAL_CONTROL: "brightness",
},
blocking=True,
)
await hass.async_block_till_done()
for profile in (switch, other):
attrs = hass.states.get(profile.entity_id).attributes
assert attrs["manual_control"] == [light.entity_id]
assert attrs["manual_control_brightness"] == [light.entity_id]
assert attrs["manual_control_color"] == []
await hass.services.async_call(
DOMAIN,
SERVICE_SET_MANUAL_CONTROL,
{ATTR_ENTITY_ID: other.entity_id, CONF_MANUAL_CONTROL: False},
blocking=True,
)
await hass.async_block_till_done()
for profile in (switch, other):
attrs = hass.states.get(profile.entity_id).attributes
assert attrs["manual_control"] == []
assert attrs["manual_control_brightness"] == []
assert attrs["manual_control_color"] == []
async def test_manual_control_state_ignores_incomplete_entries(hass):
"""An entry awaiting platform setup must not break another profile's updates."""
switch, (light, *_) = await setup_lights_and_switch(hass)
pending = MockConfigEntry(domain=DOMAIN, data={CONF_NAME: "pending"})
pending.add_to_hass(hass)
hass.data[DOMAIN][pending.entry_id] = {}
switch.manager.set_manual_control_attributes(light.entity_id)
await hass.async_block_till_done()
assert hass.states.get(switch.entity_id).attributes["manual_control"] == [
light.entity_id,
]
async def test_adaptation_attribute_selection(hass):
"""Test the 'manual control' tracking."""
switch, (light, *_) = await setup_lights_and_switch(hass)