Make instantaneous light.turn_on adaptation configurable with intercept (#750)

* Make intercept configurable

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

* import

* skip

* split

* do not pass config_entry

* add to conf

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

* spacing

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Bas Nijholt 2023-08-15 12:23:56 -07:00 committed by GitHub
commit 68feb3d931
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 57 deletions

View file

@ -231,6 +231,13 @@ DOCS[CONF_SKIP_REDUNDANT_COMMANDS] = (
"Disable if physical light states get out of sync with HA's recorded state."
)
CONF_INTERCEPT, DEFAULT_INTERCEPT = "intercept", True
DOCS[CONF_INTERCEPT] = (
"Intercept and adapt `light.turn_on` calls to enabling instantaneous color "
"and brightness adaptation. 🏎️ Disable for lights that do not "
"support `light.turn_on` with color and brightness."
)
CONF_MULTI_LIGHT_INTERCEPT, DEFAULT_MULTI_LIGHT_INTERCEPT = (
"multi_light_intercept",
True,
@ -238,7 +245,8 @@ CONF_MULTI_LIGHT_INTERCEPT, DEFAULT_MULTI_LIGHT_INTERCEPT = (
DOCS[CONF_MULTI_LIGHT_INTERCEPT] = (
"Intercept and adapt `light.turn_on` calls that target multiple lights. ➗"
"⚠️ This might result in splitting up a single `light.turn_on` call "
"into multiple calls, e.g., when lights are in different switches."
"into multiple calls, e.g., when lights are in different switches. "
"Requires `intercept` to be enabled."
)
SLEEP_MODE_SWITCH = "sleep_mode_switch"
@ -356,6 +364,7 @@ VALIDATION_TUPLES = [
DEFAULT_SKIP_REDUNDANT_COMMANDS,
bool,
),
(CONF_INTERCEPT, DEFAULT_INTERCEPT, bool),
(CONF_MULTI_LIGHT_INTERCEPT, DEFAULT_MULTI_LIGHT_INTERCEPT, bool),
(CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES, bool),
]

View file

@ -54,7 +54,8 @@
"send_split_delay": "send_split_delay",
"adapt_delay": "adapt_delay",
"skip_redundant_commands": "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.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches.",
"intercept": "intercept: Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled.",
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝"
},
"data_description": {

View file

@ -107,6 +107,7 @@ from .const import (
CONF_DETECT_NON_HA_CHANGES,
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
CONF_INITIAL_TRANSITION,
CONF_INTERCEPT,
CONF_INTERVAL,
CONF_LIGHTS,
CONF_MANUAL_CONTROL,
@ -180,11 +181,6 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
# A (non-user-configurable, thus internal) flag to control the proactive adaptation mode.
# This exists to disable the proactive adaptation in the unit tests and enable it
# only for specific unit tests and when running as integration."""
INTERNAL_CONF_PROACTIVE_SERVICE_CALL_ADAPTATION = "proactive_adaptation"
# Consider it a significant change when attribute changes more than
BRIGHTNESS_CHANGE = 25 # ≈10% of total range
COLOR_TEMP_CHANGE = 100 # ≈3% of total range (2000-6500)
@ -427,7 +423,7 @@ async def async_setup_entry( # noqa: PLR0915
return
if (manager := data.get(ATTR_ADAPTIVE_LIGHTING_MANAGER)) is None:
manager = AdaptiveLightingManager(hass, config_entry)
manager = AdaptiveLightingManager(hass)
data[ATTR_ADAPTIVE_LIGHTING_MANAGER] = manager
sleep_mode_switch = SimpleSwitch(
@ -882,7 +878,16 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._adapt_only_on_bare_turn_on = data[CONF_ADAPT_ONLY_ON_BARE_TURN_ON]
self._auto_reset_manual_control_time = data[CONF_AUTORESET_CONTROL]
self._skip_redundant_commands = data[CONF_SKIP_REDUNDANT_COMMANDS]
self._intercept = data[CONF_INTERCEPT]
self._multi_light_intercept = data[CONF_MULTI_LIGHT_INTERCEPT]
if not data[CONF_INTERCEPT] and data[CONF_MULTI_LIGHT_INTERCEPT]:
_LOGGER.warning(
"%s: Config mismatch: `multi_light_intercept` set to `true` requires `intercept`"
" to be enabled. Adjusting config and continuing setup with"
" `multi_light_intercept: false`.",
self._name,
)
self._multi_light_intercept = False
self._expand_light_groups() # updates manual control timers
location, _ = get_astral_location(self.hass)
@ -1603,11 +1608,10 @@ class SimpleSwitch(SwitchEntity, RestoreEntity):
class AdaptiveLightingManager:
"""Track 'light.turn_off' and 'light.turn_on' service calls."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the AdaptiveLightingManager that is shared among all switches."""
assert hass is not None
self.hass = hass
data = validate(config_entry)
self.lights: set[str] = set()
# Tracks 'light.turn_off' service calls
@ -1658,38 +1662,32 @@ class AdaptiveLightingManager:
self._proactively_adapting_contexts: dict[str, str] = {}
is_proactive_adaptation_enabled = data.get(
INTERNAL_CONF_PROACTIVE_SERVICE_CALL_ADAPTATION,
True,
)
try:
self.listener_removers.append(
setup_service_call_interceptor(
hass,
LIGHT_DOMAIN,
SERVICE_TURN_ON,
self._service_interceptor_turn_on_handler,
),
)
if is_proactive_adaptation_enabled:
try:
self.listener_removers.append(
setup_service_call_interceptor(
hass,
LIGHT_DOMAIN,
SERVICE_TURN_ON,
self._service_interceptor_turn_on_handler,
),
)
self.listener_removers.append(
setup_service_call_interceptor(
hass,
LIGHT_DOMAIN,
SERVICE_TOGGLE,
self._service_interceptor_turn_on_handler,
),
)
self.listener_removers.append(
setup_service_call_interceptor(
hass,
LIGHT_DOMAIN,
SERVICE_TOGGLE,
self._service_interceptor_turn_on_handler,
),
)
_LOGGER.debug("Proactive adaptation enabled")
except RuntimeError:
_LOGGER.warning(
"Failed to set up service call interceptors, "
"falling back to event-reactive mode",
exc_info=True,
)
_LOGGER.debug("Proactive adaptation enabled")
except RuntimeError:
_LOGGER.warning(
"Failed to set up service call interceptors, "
"falling back to event-reactive mode",
exc_info=True,
)
def disable(self):
"""Disable the listener by removing all subscribed handlers."""
@ -1812,6 +1810,7 @@ class AdaptiveLightingManager:
else:
if (
not switch.is_on
or not switch._intercept
# Never adapt on light groups, because HA will make a separate light.turn_on
or _is_light_group(self.hass.states.get(entity_id))
# Prevent adaptation of TURN_ON calls when light is already on,
@ -1829,12 +1828,13 @@ class AdaptiveLightingManager:
):
_LOGGER.debug(
"Switch is off or light is already on for entity_id='%s', skipped='%s'"
" (is_on='%s', is_state='%s', manual_control='%s')",
" (is_on='%s', is_state='%s', manual_control='%s', switch._intercept='%s')",
entity_id,
skipped,
switch.is_on,
self.hass.states.is_state(entity_id, STATE_ON),
self.manual_control.get(entity_id, False),
switch._intercept,
)
skipped.append(entity_id)
else:

View file

@ -55,7 +55,8 @@
"send_split_delay": "send_split_delay",
"adapt_delay": "adapt_delay",
"skip_redundant_commands": "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.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches.",
"intercept": "intercept: Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled.",
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝"
},
"data_description": {