diff --git a/custom_components/adaptive_lighting/adaptation_utils.py b/custom_components/adaptive_lighting/adaptation_utils.py index b4d6c3bd..e108910b 100644 --- a/custom_components/adaptive_lighting/adaptation_utils.py +++ b/custom_components/adaptive_lighting/adaptation_utils.py @@ -295,6 +295,9 @@ def prepare_adaptation_data( for key in (ATTR_ENTITY_ID, ATTR_BRIGHTNESS, ATTR_TRANSITION) if key in service_data } + # The current state can retain brightness zero while the light is off. + # Keep this command available to intercept a new bare turn-on. + filter_by_state = False service_datas = _split_service_call_data(service_data) if split else [service_data] service_datas_length = len(service_datas) diff --git a/tests/test_adaptation_utils.py b/tests/test_adaptation_utils.py index 3cb24dae..fde9ab60 100644 --- a/tests/test_adaptation_utils.py +++ b/tests/test_adaptation_utils.py @@ -484,6 +484,30 @@ async def test_prepare_zero_after_shared_brightness_was_applied(hass_states_mock assert data.attributes is LightControlAttributes.NONE +async def test_prepare_zero_is_not_filtered_for_off_light(hass_states_mock): + """A retained zero remains available to intercept a bare turn-on.""" + hass_states_mock.states.get.return_value = Mock(attributes={ATTR_BRIGHTNESS: 0}) + data = prepare_adaptation_data( + hass_states_mock, + "light.test", + Context(id="test-id"), + 1, + 0.2, + { + ATTR_ENTITY_ID: "light.test", + ATTR_BRIGHTNESS: 0, + ATTR_COLOR_TEMP_KELVIN: 4000, + }, + split=True, + filter_by_state=True, + force=False, + ) + + assert [item async for item in data.service_call_datas] == [ + {ATTR_ENTITY_ID: "light.test", ATTR_BRIGHTNESS: 0}, + ] + + @pytest.fixture(name="hass_states_mock") def fixture_hass_states_mock(): """Mocks a HA state machine which returns a mock state.""" diff --git a/tests/test_switch.py b/tests/test_switch.py index 02cc8f40..28923516 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -5213,3 +5213,57 @@ async def test_zero_sleep_multi_light_intercept_has_no_follow_up(hass): ] assert payloads == [{ATTR_ENTITY_ID: managed}] assert all(hass.states.get(entity_id).state == STATE_OFF for entity_id in managed) + + +@pytest.mark.parametrize("split", [False, True]) +@pytest.mark.parametrize("skip_redundant", [False, True]) +async def test_zero_sleep_intercepts_off_light_reporting_zero( + hass, + split, + skip_redundant, +): + """A retained zero remains an off command during intercepted turn-on.""" + switch, (light, *_) = await setup_lights_and_switch( + hass, + { + CONF_INTERCEPT: True, + CONF_SEPARATE_TURN_ON_COMMANDS: split, + CONF_SKIP_REDUNDANT_COMMANDS: skip_redundant, + CONF_SLEEP_BRIGHTNESS: 0, + }, + ) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: light.entity_id}, + blocking=True, + ) + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: switch.sleep_mode_switch.entity_id}, + blocking=True, + ) + await _finish_zero_sleep_adaptations(hass, switch) + + attributes = dict(hass.states.get(light.entity_id).attributes) + attributes[ATTR_BRIGHTNESS] = 0 + set_light_brightness(light, 0) + hass.states.async_set(light.entity_id, STATE_OFF, attributes) + await hass.async_block_till_done() + + events = [] + remove_listener = hass.bus.async_listen(EVENT_CALL_SERVICE, events.append) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: light.entity_id}, + blocking=True, + ) + await _finish_zero_sleep_adaptations(hass, switch) + remove_listener() + + assert _light_turn_on_payloads(events, light.entity_id) == [ + {ATTR_ENTITY_ID: light.entity_id}, + ] + assert hass.states.get(light.entity_id).state == STATE_OFF