Only start adapting on light.turn_on when detect_non_ha_changes: false to prevent unwanted light turn ons (#663)

* Fixes in maybe_cancel_adjusting to possibly fix accidental turn on

* simplify logic in maybe_cancel_adjusting

* Add logging statements

* only control if turn_on called

* more logs

* add TODO

* Add _state_event_is_from_our_turn_on

* Ignore off->on state switches that are not accociated with light.turn_on

* improve logging

* rename

* Update docs

* Update README.md, strings.json, and services.yaml

* Add caution message to README

* Change order of emojis

* Update README.md, strings.json, and services.yaml

* Check that platform is not None

* log the call

* fix args

* Do not re-add already added configs

* Do not re-add already added configs

* Use async_remove

* remove unused code

* [pre-commit.ci] pre-commit autoupdate (#627)

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.279 → v0.0.280](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.279...v0.0.280)
- [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <basnijholt@gmail.com>

* Extra logging statement

* Add to README

* log context_id

* return right indent

* move comment

* Skip on self.manager.is_proactively_adapting

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Bas Nijholt 2023-07-28 15:17:17 -07:00 committed by GitHub
commit 89c90adde0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 110 deletions

View file

@ -33,9 +33,11 @@ Adaptive Lighting is designed to automatically detect when you or another source
When this occurs, the affected light is marked as "manually controlled," and Adaptive Lighting will not make further adjustments until the light is turned off and back on or reset using the `adaptive_lighting.set_manual_control` service call.
This feature is available when `take_over_control` is enabled.
Additionally, enabling detect_non_ha_changes allows Adaptive Lighting to detect all state changes, including those made outside of Home Assistant, by comparing the light's state to its previously used settings.
Additionally, enabling `detect_non_ha_changes` allows Adaptive Lighting to detect all state changes, including those made outside of Home Assistant, by comparing the light's state to its previously used settings.
The `adaptive_lighting.manual_control` event is fired when a light is marked as "manually controlled," allowing for integration with automations 🤖.
> ⚠️ **_Caution: Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable `detect_non_ha_changes` if you encounter such issues._**
## :books: Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -91,38 +93,38 @@ The YAML and frontend configuration methods support all of the options listed be
<!-- START_OUTPUT -->
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
| Variable name | Description | Default | Type |
|:-------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------------------------|
| `lights` | List of light entity_ids to be controlled (may be empty). 🌟 | `[]` | list of `entity_id`s |
| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | `False` | `bool` |
| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` |
| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 |
| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 |
| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 |
| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` |
| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` |
| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 |
| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 |
| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 |
| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 |
| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 |
| `sleep_rgb_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` |
| `sleep_color_temp` | Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴 | `1000` | `int` 1000-10000 |
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color |
| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` |
| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅 | `None` | `str` |
| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` |
| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇 | `None` | `str` |
| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` |
| `take_over_control` | Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒 | `True` | `bool` |
| `detect_non_ha_changes` | Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️ | `False` | `bool` |
| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` |
| `send_split_delay` | Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️ | `0` | `int` 0-10000 |
| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️ | `0` | `float > 0` |
| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-31536000 |
| `skip_redundant_commands` | Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. Disable if physical light states get out of sync with HA's recorded state. | `False` | `bool` |
| Variable name | Description | Default | Type |
|:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------------------------|
| `lights` | List of light entity_ids to be controlled (may be empty). 🌟 | `[]` | list of `entity_id`s |
| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | `False` | `bool` |
| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` |
| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 |
| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 |
| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 |
| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` |
| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` |
| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 |
| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 |
| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 |
| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 |
| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 |
| `sleep_rgb_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` |
| `sleep_color_temp` | Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴 | `1000` | `int` 1000-10000 |
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color |
| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` |
| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅 | `None` | `str` |
| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` |
| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇 | `None` | `str` |
| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` |
| `take_over_control` | Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒 | `True` | `bool` |
| `detect_non_ha_changes` | Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues. | `False` | `bool` |
| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` |
| `send_split_delay` | Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️ | `0` | `int` 0-10000 |
| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️ | `0` | `float > 0` |
| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-31536000 |
| `skip_redundant_commands` | Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. Disable if physical light states get out of sync with HA's recorded state. | `False` | `bool` |
<!-- END_OUTPUT -->
@ -341,7 +343,11 @@ Adaptive Lighting sends more commands to lights than a typical human user would.
- Unresponsive lights.
- Home Assistant reporting incorrect light states, causing Adaptive Lighting to inadvertently turn lights back on.
Most issues that appear to be caused by Adaptive Lighting are actually due to unrelated problems. Addressing these issues will significantly improve your Home Assistant experience.
Most issues that appear to be caused by Adaptive Lighting are actually due to unrelated problems.
Addressing these issues will significantly improve your Home Assistant experience.
In case lights are suddenly turning on by themselves, this is most likely due to the light incorrectly reporting an "on" state to Home Assistant, leading to an undesired Adaptive Lighting action.
To prevent adapting in cases *where the state of the light is suddenly "on" and only adapt if there is an associated `light.turn_on` service call*, set `detect_non_ha_changes: false`.
#### :signal_strength: WiFi Networks

View file

@ -29,8 +29,11 @@ CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES = (
False,
)
DOCS[CONF_DETECT_NON_HA_CHANGES] = (
"Detect non-`light.turn_on` state changes and stop adapting lights. "
"Requires `take_over_control`. 🕵️"
"Detects and halts adaptations for non-`light.turn_on` state changes. "
"Needs `take_over_control` enabled. 🕵️ "
"Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result "
"in lights turning on unexpectedly. "
"Disable this feature if you encounter such issues."
)
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES = (

View file

@ -53,7 +53,11 @@ def setup_service_call_interceptor(
call.data = ReadOnlyDict(data)
except Exception as e: # noqa: BLE001
# Blindly catch all exceptions to avoid breaking light.turn_on
_LOGGER.error("Error in service_func_proxy: %s", e)
_LOGGER.error(
"Error for call '%s' in service_func_proxy: '%s'",
call.data,
e,
)
# Call original service handler with processed data
await existing_service.job.target(call)

View file

@ -226,7 +226,7 @@ change_switch_settings:
selector:
boolean: null
detect_non_ha_changes:
description: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️
description: 'Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an ''on'' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues.'
required: false
example: false
selector:

View file

@ -43,7 +43,7 @@
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
"take_over_control": "take_over_control: Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒",
"detect_non_ha_changes": "detect_non_ha_changes: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
"detect_non_ha_changes": "detect_non_ha_changes: Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues.",
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
"adapt_delay": "adapt_delay: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
@ -215,7 +215,7 @@
"name": "take_over_control"
},
"detect_non_ha_changes": {
"description": "Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
"description": "Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues.",
"name": "detect_non_ha_changes"
},
"transition": {

View file

@ -594,6 +594,7 @@ async def async_setup_entry( # noqa: PLR0915
if k not in skip:
args[vol.Optional(k)] = valid
platform = entity_platform.current_platform.get()
assert platform is not None
platform.async_register_entity_service(
SERVICE_CHANGE_SWITCH_SETTINGS,
args,
@ -855,9 +856,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._icon = ICON_MAIN
self._state = None
# Tracks 'off' → 'on' state changes
self._on_to_off_event: dict[str, Event] = {}
# Tracks 'on' → 'off' state changes
self._on_to_off_event: dict[str, Event] = {}
# Tracks 'off' → 'on' state changes
self._off_to_on_event: dict[str, Event] = {}
# Locks that prevent light adjusting when waiting for a light to 'turn_off'
self._locks: dict[str, asyncio.Lock] = {}
@ -1443,10 +1444,11 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
else:
_LOGGER.debug(
"%s: Calling _adapt_light from _update_attrs_and_maybe_adapt_lights:"
" '%s' with transition %s",
" '%s' with transition %s and context.id=%s",
self._name,
light,
transition,
context.id,
)
await self._adapt_light(light, transition, context=context)
@ -1471,12 +1473,24 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
old_state = event.data.get("old_state")
new_state = event.data.get("new_state")
entity_id = event.data.get("entity_id")
if (
old_state is not None
and old_state.state == STATE_OFF
and new_state is not None
and new_state.state == STATE_ON
):
if old_state is None or new_state is None:
return
if old_state.state == STATE_ON and new_state.state == STATE_OFF:
# Tracks 'on' → 'off' state changes
self._on_to_off_event[entity_id] = event
self.manager.reset(entity_id)
_LOGGER.debug(
"%s: Detected an 'on''off' event for '%s' with context.id='%s'",
self._name,
entity_id,
event.context.id,
)
if old_state.state == STATE_OFF and new_state.state == STATE_ON:
# Tracks 'off' → 'on' state changes
self._off_to_on_event[entity_id] = event
_LOGGER.debug(
"%s: Detected an 'off''on' event for '%s' with context.id='%s'",
self._name,
@ -1484,13 +1498,36 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
event.context.id,
)
if (
not self._detect_non_ha_changes
and not self.manager.is_proactively_adapting(event.context.id)
and not self.manager._off_to_on_state_event_is_from_turn_on(
entity_id,
event,
)
):
# If we don't detect non-HA changes, we're only adjusting lights that
# were turned on by HA. If the light was turned on by something else,
# we don't adjust it (e.g., when HA suddenly reports it as on).
# Sometimes the light incorrectly reports itself as on when it's
# actually off. This code path will ensure that the light is
# not controlled by Adaptive Lighting.
_LOGGER.debug(
"%s: Ignoring 'off''on' event for '%s' with context.id='%s'"
" because 'light.turn_on' was not called by HA and"
" 'detect_non_ha_changes' is False",
self._name,
entity_id,
event.context.id,
)
self.manager.mark_as_manual_control(entity_id)
return
if event.context.parent_id and not self.manager.is_proactively_adapting(
event.context.id,
):
self.manager.reset(entity_id, reset_manual_control=False)
# Tracks 'off' → 'on' state changes
self._off_to_on_event[entity_id] = event
lock = self._locks.setdefault(entity_id, asyncio.Lock())
async with lock:
if await self.manager.maybe_cancel_adjusting(
@ -1527,15 +1564,6 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
force=True,
context=self.create_context("light_event", parent=event.context),
)
elif (
old_state is not None
and old_state.state == STATE_ON
and new_state is not None
and new_state.state == STATE_OFF
):
# Tracks 'off' → 'on' state changes
self._on_to_off_event[entity_id] = event
self.manager.reset(entity_id)
class SimpleSwitch(SwitchEntity, RestoreEntity):
@ -2365,6 +2393,8 @@ class AdaptiveLightingManager:
detected, we mark the light as 'manually controlled' until the light
or switch is turned 'off' and 'on' again.
"""
assert switch._detect_non_ha_changes
last_service_data = self.last_service_data.get(light)
if last_service_data is None:
return None
@ -2379,44 +2409,49 @@ class AdaptiveLightingManager:
# Ensure HASS is correctly updating your light's state with
# light.turn_on calls if any problems arise. This
# can happen e.g. using zigbee2mqtt with 'report: false' in device settings.
if switch._detect_non_ha_changes:
await self.hass.helpers.entity_component.async_update_entity(light)
refreshed_state = self.hass.states.get(light)
changed = compare_to(
old_attributes=last_service_data,
new_attributes=refreshed_state.attributes,
)
if changed:
_LOGGER.debug(
"%s: 'detect_non_ha_changes: true', calling update_entity(%s)"
" and check if it's last adapt succeeded.",
"%s: State attributes of '%s' (%s) didn't change wrt 'last_service_data' (%s) (context.id=%s)",
switch._name,
light,
refreshed_state.attributes,
last_service_data,
context.id,
)
# This update_entity probably isn't necessary now that we're checking
# if transitions finished from our last adapt.
await self.hass.helpers.entity_component.async_update_entity(light)
refreshed_state = self.hass.states.get(light)
_LOGGER.debug(
"%s: Current state of %s: %s",
switch._name,
light,
refreshed_state,
)
changed = compare_to(
old_attributes=last_service_data,
new_attributes=refreshed_state.attributes,
)
if changed:
_LOGGER.debug(
"State of '%s' didn't change wrt 'last_service_data' (context.id=%s)",
light,
context.id,
)
return True
return True
_LOGGER.debug(
"%s: Light '%s' correctly matches our last adapt's service data, continuing..."
" context.id=%s.",
"%s: State attributes of '%s' (%s) changed wrt 'last_service_data' (%s) (context.id=%s)",
switch._name,
light,
refreshed_state.attributes,
last_service_data,
context.id,
)
return False
async def maybe_cancel_adjusting( # noqa: PLR0911, PLR0912
def _off_to_on_state_event_is_from_turn_on(
self,
entity_id: str,
off_to_on_event: Event,
) -> bool:
# Adaptive Lighting should never turn on lights itself
assert not is_our_context(off_to_on_event.context)
turn_on_event: Event | None = self.turn_on_event.get(entity_id)
id_off_to_on = off_to_on_event.context.id
return (
turn_on_event is not None
and id_off_to_on is not None
and id_off_to_on == turn_on_event.context.id
)
async def maybe_cancel_adjusting( # noqa: PLR0911
self,
entity_id: str,
off_to_on_event: Event,
@ -2435,7 +2470,11 @@ class AdaptiveLightingManager:
adjust the lights.
"""
if on_to_off_event is None:
# No state change has been registered before.
_LOGGER.debug(
"maybe_cancel_adjusting: No 'on''off' state change has been registered before for '%s'."
" It's possible that the light was already on when Home Assistant was turned on.",
entity_id,
)
return False
id_on_to_off = on_to_off_event.context.id
@ -2446,18 +2485,10 @@ class AdaptiveLightingManager:
else:
transition = None
turn_on_event = self.turn_on_event.get(entity_id)
if turn_on_event is None:
# This means that the light never got a 'turn_on' call that we
# registered. I am not 100% sure why this happens, but it does.
# This is a fix for #170 and #232.
return False
id_turn_on = turn_on_event.context.id
id_off_to_on = off_to_on_event.context.id
if id_off_to_on == id_turn_on and id_off_to_on is not None:
# State change 'off' → 'on' triggered by 'light.turn_on'.
if self._off_to_on_state_event_is_from_turn_on(entity_id, off_to_on_event):
_LOGGER.debug(
"maybe_cancel_adjusting: State change 'off''on' triggered by 'light.turn_on'",
)
return False
if (
@ -2476,6 +2507,11 @@ class AdaptiveLightingManager:
delta_time = (dt_util.utcnow() - on_to_off_event.time_fired).total_seconds()
if delta_time > delay:
_LOGGER.debug(
"maybe_cancel_adjusting: delta_time='%s' > delay='%s'",
delta_time,
delay,
)
return False
# Here we could just `return True` but because we want to prevent any updates
@ -2484,23 +2520,33 @@ class AdaptiveLightingManager:
# is 'off' or the time has passed.
delay -= delta_time # delta_time has passed since the 'off' → 'on' event
_LOGGER.debug("Waiting with adjusting '%s' for %s", entity_id, delay)
_LOGGER.debug(
"maybe_cancel_adjusting: Waiting with adjusting '%s' for %s",
entity_id,
delay,
)
total_sleep = 0
for _ in range(3):
# It can happen that the actual transition time is longer than the
# specified time in the 'turn_off' service.
coro = asyncio.sleep(delay)
total_sleep += delay
task = self.sleep_tasks[entity_id] = asyncio.ensure_future(coro)
try:
await task
except asyncio.CancelledError: # 'light.turn_on' has been called
_LOGGER.debug(
"Sleep task is cancelled due to 'light.turn_on('%s')' call",
"maybe_cancel_adjusting: Sleep task is cancelled due to 'light.turn_on('%s')' call",
entity_id,
)
return False
if not is_on(self.hass, entity_id):
_LOGGER.debug(
"maybe_cancel_adjusting: '%s' is off after %s seconds, cancelling adaptation",
entity_id,
total_sleep,
)
return True
delay = TURNING_OFF_DELAY # next time only wait this long
@ -2511,10 +2557,12 @@ class AdaptiveLightingManager:
return True
# Now we assume that the lights are still on and they were intended
# to be on. In case this still gives problems for some, we might
# choose to **only** adapt on 'light.turn_on' events and ignore
# other 'off' → 'on' state switches resulting from polling. That
# would mean we 'return True' here.
# to be on.
_LOGGER.debug(
"maybe_cancel_adjusting: '%s' is still on after %s seconds, assuming it was intended to be on",
entity_id,
total_sleep,
)
return False

View file

@ -44,7 +44,7 @@
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
"take_over_control": "take_over_control: Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒",
"detect_non_ha_changes": "detect_non_ha_changes: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
"detect_non_ha_changes": "detect_non_ha_changes: Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues.",
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
"adapt_delay": "adapt_delay: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
@ -216,7 +216,7 @@
"name": "take_over_control"
},
"detect_non_ha_changes": {
"description": "Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
"description": "Detects and halts adaptations for non-`light.turn_on` state changes. Needs `take_over_control` enabled. 🕵️ Caution: ⚠️ Some lights might falsely indicate an 'on' state, which could result in lights turning on unexpectedly. Disable this feature if you encounter such issues.",
"name": "detect_non_ha_changes"
},
"transition": {

View file

@ -1478,7 +1478,7 @@ async def test_proactive_adaptation_with_separate_commands(hass):
)
# Expect two service calls
assert len(event_context_ids) == 2
assert len(event_context_ids) == 2, event_context_ids
assert event_context_ids[0] == "test_context"
assert is_our_context_id(event_context_ids[1])