From e0e04da0f6ea230fa4738d24fc456696c2806a7b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 2 Aug 2023 23:11:45 -0700 Subject: [PATCH] Before scheduling turn_on do a last-minute check if lights are off (#671) * Before scheduling turn_on do a last-minute check if lights are off * Pass force * add comment * fix for proactive * commetn * No default for force * rm newline * no force * Return bool --- .../adaptive_lighting/adaptation_utils.py | 3 +++ custom_components/adaptive_lighting/switch.py | 23 ++++++++++++++++++- tests/test_adaptation_utils.py | 1 + tests/test_switch.py | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/custom_components/adaptive_lighting/adaptation_utils.py b/custom_components/adaptive_lighting/adaptation_utils.py index e0ab6c47..97914327 100644 --- a/custom_components/adaptive_lighting/adaptation_utils.py +++ b/custom_components/adaptive_lighting/adaptation_utils.py @@ -141,6 +141,7 @@ class AdaptationData: context: Context sleep_time: float service_call_datas: AsyncGenerator[ServiceData, None] + force: bool max_length: int which: Literal["brightness", "color", "both"] initial_sleep: bool = False @@ -179,6 +180,7 @@ def prepare_adaptation_data( service_data: ServiceData, split: bool, filter_by_state: bool, + force: bool, ) -> AdaptationData: """Prepares a data object carrying all data required to execute an adaptation.""" _LOGGER.debug( @@ -209,6 +211,7 @@ def prepare_adaptation_data( context=context, sleep_time=sleep_time, service_call_datas=service_data_iterator, + force=force, max_length=service_datas_length, which=lighting_type, ) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index b6f8a24e..641ffab6 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -537,6 +537,7 @@ async def async_setup_entry( # noqa: PLR0915 adapt_brightness=data[ATTR_ADAPT_BRIGHTNESS], adapt_color=data[ATTR_ADAPT_COLOR], prefer_rgb_color=data[CONF_PREFER_RGB_COLOR], + force=True, ) @callback @@ -1171,6 +1172,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): adapt_brightness: bool | None = None, adapt_color: bool | None = None, prefer_rgb_color: bool | None = None, + force: bool = False, context: Context | None = None, ) -> AdaptationData | None: """Prepare `AdaptationData` for adapting a light.""" @@ -1257,6 +1259,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): service_data, split=self._separate_turn_on_commands, filter_by_state=self._skip_redundant_commands, + force=force, ) async def _adapt_light( @@ -1267,6 +1270,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): adapt_brightness: bool | None = None, adapt_color: bool | None = None, prefer_rgb_color: bool | None = None, + force: bool = False, ) -> None: # This should never happen if it's been proactively adapted. # The context.parent_id is the context.id of the service call that was intercepted @@ -1283,6 +1287,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): adapt_brightness, adapt_color, prefer_rgb_color, + force, context, ) if data is None: @@ -1308,6 +1313,20 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): # All service datas processed break + if ( + not data.force + and not is_on(self.hass, data.entity_id) + # if proactively adapting, we are sure that it came from a `light.turn_on` + and not self.manager.is_proactively_adapting(data.context.id) + ): + # Do a last-minute check if the entity is still on. + _LOGGER.debug( + "%s: Skipping adaptation of %s because it is now off", + self._name, + data.entity_id, + ) + return + _LOGGER.debug( "%s: Scheduling 'light.turn_on' with the following 'service_data': %s" " with context.id='%s'", @@ -1440,6 +1459,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): self._take_over_control and self._detect_non_ha_changes and not force + # Note: This call updates the state of the light + # so it might suddenly be off. and await self.manager.significant_change( self, light, @@ -1460,7 +1481,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): transition, context.id, ) - await self._adapt_light(light, context, transition) + await self._adapt_light(light, context, transition, force=force) async def _respond_to_off_to_on_event(self, entity_id: str, event: Event) -> None: assert not self.manager.is_proactively_adapting(event.context.id) diff --git a/tests/test_adaptation_utils.py b/tests/test_adaptation_utils.py index fc0df739..20db5037 100644 --- a/tests/test_adaptation_utils.py +++ b/tests/test_adaptation_utils.py @@ -312,6 +312,7 @@ async def test_prepare_adaptation_data( service_data, split, filter_by_state, + force=False, ) generated_service_datas = [item async for item in data.service_call_datas] diff --git a/tests/test_switch.py b/tests/test_switch.py index c7220ee4..4963209b 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -1342,6 +1342,7 @@ async def test_cancellable_service_calls_task(hass): context, 0, _create_service_call_data_iterator(hass, [service_data], False), + force=False, max_length=1, which="both", )