From 2db675a434567c535160929dbbc9a0a57bdd8354 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Mon, 10 Apr 2023 05:25:44 -0500 Subject: [PATCH] merge with `alt_detect_method` required for this PR --- custom_components/adaptive_lighting/const.py | 7 + custom_components/adaptive_lighting/switch.py | 141 +++++++++++++++--- 2 files changed, 125 insertions(+), 23 deletions(-) diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index 64620927..e3557e0c 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -33,6 +33,12 @@ DOCS[CONF_DETECT_NON_HA_CHANGES] = ( "Requires `take_over_control`. 🕵️" ) +CONF_ALT_DETECT_METHOD, DEFAULT_ALT_DETECT_METHOD = "alt_detect_method", False +DOCS[CONF_ALT_DETECT_METHOD] = ( + "alt_detect_method: When true, will check for any significant changes in the opposite direction" + " of where adaptive-lighting tried to adapt last." + " This is an alternative to 'detect_non_ha_changes' (default: false)" +) CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES = ( "include_config_in_attributes", False, @@ -262,6 +268,7 @@ VALIDATION_TUPLES = [ (CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int), (CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE, bool), (CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL, bool), + (CONF_ALT_DETECT_METHOD, DEFAULT_ALT_DETECT_METHOD, bool), (CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES, bool), (CONF_SEPARATE_TURN_ON_COMMANDS, DEFAULT_SEPARATE_TURN_ON_COMMANDS, bool), (CONF_SEND_SPLIT_DELAY, DEFAULT_SEND_SPLIT_DELAY, int_between(0, 10000)), diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 98747a17..426ef60e 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -99,6 +99,7 @@ from .const import ( ATTR_TURN_ON_OFF_LISTENER, CONF_ADAPT_DELAY, CONF_ADAPT_UNTIL_SLEEP, + CONF_ALT_DETECT_METHOD, CONF_AUTORESET_CONTROL, CONF_DETECT_NON_HA_CHANGES, CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, @@ -671,6 +672,15 @@ def color_difference_redmean( return math.sqrt(red_term + green_term + blue_term) +def check_direction_change(last: int, current: int, last_adapt_value: int) -> bool: + _LOGGER.debug("compare direction: current value %s to last value %s", current, last) + if last_adapt_value < last: # Value adapting down + return current > last or current < last_adapt_value + elif last_adapt_value > last: # Value adapting up + return current < last or current > last_adapt_value + return False + + # All comparisons should be done with RGB since # converting anything to color temp is inaccurate. def _convert_attributes(attributes: dict[str, Any]) -> dict[str, Any]: @@ -713,6 +723,7 @@ def _attributes_have_changed( adapt_brightness: bool, adapt_color: bool, context: Context, + last_adapt_attempt=None, ) -> bool: if adapt_color: old_attributes, new_attributes = _add_missing_attributes( @@ -727,15 +738,33 @@ def _attributes_have_changed( last_brightness = old_attributes[ATTR_BRIGHTNESS] current_brightness = new_attributes[ATTR_BRIGHTNESS] if abs(current_brightness - last_brightness) > BRIGHTNESS_CHANGE: - _LOGGER.debug( - "Brightness of '%s' significantly changed from %s to %s with" - " context.id='%s'", - light, - last_brightness, - current_brightness, - context.id, - ) - return True + if last_adapt_attempt: + changed = check_direction_change( + last_brightness, + current_brightness, + last_adapt_attempt[ATTR_BRIGHTNESS], + ) + _LOGGER.debug( + "altdetect: Brightness of '%s' changed from %s to %s intended %s with" + " context.id='%s' Significant? %s", + light, + last_brightness, + current_brightness, + last_adapt_attempt[ATTR_BRIGHTNESS], + context.id, + changed, + ) + return changed + else: + _LOGGER.debug( + "Brightness of '%s' significantly changed from %s to %s with" + " context.id='%s'", + light, + last_brightness, + current_brightness, + context.id, + ) + return True if ( adapt_color @@ -745,15 +774,33 @@ def _attributes_have_changed( last_color_temp = old_attributes[ATTR_COLOR_TEMP_KELVIN] current_color_temp = new_attributes[ATTR_COLOR_TEMP_KELVIN] if abs(current_color_temp - last_color_temp) > COLOR_TEMP_CHANGE: - _LOGGER.debug( - "Color temperature of '%s' significantly changed from %s to %s with" - " context.id='%s'", - light, - last_color_temp, - current_color_temp, - context.id, - ) - return True + if last_adapt_attempt: + changed = check_direction_change( + last_color_temp, + current_color_temp, + last_adapt_attempt[ATTR_COLOR_TEMP_KELVIN], + ) + _LOGGER.debug( + "altdetect: Color temperature of '%s' changed from %s to %s intended %s with" + " context.id='%s' Significant? %s", + light, + last_color_temp, + current_color_temp, + last_adapt_attempt[ATTR_COLOR_TEMP_KELVIN], + context.id, + changed, + ) + return changed + else: + _LOGGER.debug( + "Color temperature of '%s' significantly changed from %s to %s with" + " context.id='%s'", + light, + last_color_temp, + current_color_temp, + context.id, + ) + return True if ( adapt_color @@ -874,12 +921,18 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): self._adapt_delay = data[CONF_ADAPT_DELAY] self._send_split_delay = data[CONF_SEND_SPLIT_DELAY] self._take_over_control = data[CONF_TAKE_OVER_CONTROL] + self._alt_detect_method = data[CONF_ALT_DETECT_METHOD] self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES] - if not data[CONF_TAKE_OVER_CONTROL] and data[CONF_DETECT_NON_HA_CHANGES]: - _LOGGER.warning( - "%s: Config mismatch: 'detect_non_ha_changes: true' " - "requires 'take_over_control' to be enabled. Adjusting config " - "and continuing setup with `take_over_control: true`.", + if not data[CONF_TAKE_OVER_CONTROL] and ( + data[CONF_ALT_DETECT_METHOD] or data[CONF_DETECT_NON_HA_CHANGES] + ): + _LOGGER.warn( + "%s: Config mismatch: 'alt_detect_method: true'" + " OR 'detect_non_ha_changes: true' are set in config, however required" + " variable 'take_over_control' is turned off. Please check your" + " configuration to ensure desired functionality. We will now" + " enable 'take_over_control' and continue setting up the" + " adaptive-lighting integration normally.", self._name, ) self._take_over_control = True @@ -1234,6 +1287,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): transition: int | None, force: bool, context: Context | None, + adapt_brightness: bool | None = None, + adapt_color: bool | None = None, ) -> None: assert context is not None _LOGGER.debug( @@ -1946,6 +2001,46 @@ class TurnOnOffListener: adapt_color=adapt_color, context=context, ) + if switch._alt_detect_method: + old_states: list[State] = self.last_state_change[light] + _LOGGER.debug("Total state changes detected: %s", len(old_states)) + _LOGGER.debug( + "%s: 'alt_detect_method: true', check all state changes made to light %s", + switch._name, + light, + ) + for index, old_state in enumerate(old_states): + # The first entry of old_states should always be the + # same as last_service_data[light], and can be ignored. + if index <= 1: + continue + _LOGGER.debug( + "%s: checking for a manual change between index %s and %s...", + switch._name, + index, + index - 1, + ) + prior_state = old_states[index - 1] + if compare_to( + old_attributes=prior_state.attributes, + new_attributes=old_state.attributes, + last_adapt_attempt=last_service_data, + ): + _LOGGER.info( + "Found unexpected state_change event for %s nr. %s (context.id=%s)" + " old_state=%s\nprior_state=%s", + light, + index, + context.id, + old_state, + prior_state, + ) + _LOGGER.info( + "We will now set %s as manually controlled. (context.id=%s)", + light, + context.id, + ) + return True # Update state and check for a manual change not done in HA. # Ensure HASS is correctly updating your light's state with # light.turn_on calls if any problems arise. This