Skip to content

Commit

Permalink
Revert supported_features from #565 and #575, fixes #601 (#637)
Browse files Browse the repository at this point in the history
  • Loading branch information
basnijholt authored Jul 20, 2023
1 parent fd5f6bf commit 19d503b
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 156 deletions.
2 changes: 0 additions & 2 deletions custom_components/adaptive_lighting/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,6 @@ def int_between(min_int, max_int):
),
]

CONST_COLOR = "color"


def timedelta_as_int(value):
"""Convert a `datetime.timedelta` object to an integer.
Expand Down
109 changes: 38 additions & 71 deletions custom_components/adaptive_lighting/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR,
ATTR_MAX_COLOR_TEMP_KELVIN,
ATTR_MIN_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION,
ATTR_XY_COLOR,
Expand Down Expand Up @@ -141,7 +136,6 @@
CONF_TRANSITION,
CONF_TURN_ON_LIGHTS,
CONF_USE_DEFAULTS,
CONST_COLOR,
DOMAIN,
EXTRA_VALIDATION,
ICON_BRIGHTNESS,
Expand All @@ -163,21 +157,10 @@
from .hass_utils import setup_service_call_interceptor

_SUPPORT_OPTS = {
COLOR_MODE_BRIGHTNESS: SUPPORT_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP: SUPPORT_COLOR_TEMP,
CONST_COLOR: SUPPORT_COLOR,
ATTR_TRANSITION: SUPPORT_TRANSITION,
}


VALID_COLOR_MODES = {
COLOR_MODE_BRIGHTNESS: ATTR_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP_KELVIN,
COLOR_MODE_HS: ATTR_HS_COLOR,
COLOR_MODE_RGB: ATTR_RGB_COLOR,
COLOR_MODE_RGBW: ATTR_RGBW_COLOR,
COLOR_MODE_RGBWW: ATTR_RGBWW_COLOR,
COLOR_MODE_XY: ATTR_XY_COLOR,
"brightness": SUPPORT_BRIGHTNESS,
"color_temp": SUPPORT_COLOR_TEMP,
"color": SUPPORT_COLOR,
"transition": SUPPORT_TRANSITION,
}

_ORDER = (SUN_EVENT_SUNRISE, SUN_EVENT_NOON, SUN_EVENT_SUNSET, SUN_EVENT_MIDNIGHT)
Expand Down Expand Up @@ -625,53 +608,36 @@ def _expand_light_groups(hass: HomeAssistant, lights: list[str]) -> list[str]:
return list(all_lights)


def _supported_to_attributes(supported):
supported_attributes = {}
supports_colors = False
for mode in supported:
attr = VALID_COLOR_MODES.get(mode)
if attr:
supported_attributes[attr] = True
if attr in COLOR_ATTRS:
supports_colors = True
# ATTR_SUPPORTED_FEATURES only
elif mode in _SUPPORT_OPTS:
supported_attributes[mode] = True
if CONST_COLOR in supported_attributes:
supports_colors = True
supported_attributes.pop(CONST_COLOR)
return supported_attributes, supports_colors


def _supported_features(hass: HomeAssistant, light: str):
state = hass.states.get(light)
legacy_supported_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
legacy_supported = {
key for key, value in _SUPPORT_OPTS.items() if legacy_supported_features & value
supported_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
supported = {
key for key, value in _SUPPORT_OPTS.items() if supported_features & value
}
supported_color_modes = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES, set())
supported, supports_colors = _supported_to_attributes(
legacy_supported.union(supported_color_modes)
)
min_kelvin = state.attributes.get(ATTR_MIN_COLOR_TEMP_KELVIN)
max_kelvin = state.attributes.get(ATTR_MAX_COLOR_TEMP_KELVIN)
supported.update(
{
ATTR_MIN_COLOR_TEMP_KELVIN: min_kelvin,
ATTR_MAX_COLOR_TEMP_KELVIN: max_kelvin,
}
)
if supports_colors:
if COLOR_MODE_RGB in supported_color_modes:
supported.add("color")
# Adding brightness here, see
# comment https://github.com/basnijholt/adaptive-lighting/issues/112#issuecomment-836944011
supported[ATTR_BRIGHTNESS] = True
if CONST_COLOR not in legacy_supported:
# supports_colors = False
_LOGGER.debug(
"'supported_color_modes' supports color but the legacy 'supported_features'"
" bitfield says we do not. Despite this we'll assume light '%s' supports colors",
)
return supported, supports_colors
supported.add("brightness")
if COLOR_MODE_RGBW in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_RGBWW:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_XY in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_HS in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_COLOR_TEMP in supported_color_modes:
supported.add("color_temp")
supported.add("brightness") # see above url
if COLOR_MODE_BRIGHTNESS in supported_color_modes:
supported.add("brightness")
return supported


def color_difference_redmean(
Expand Down Expand Up @@ -1122,13 +1088,13 @@ async def prepare_adaptation_data(

# Build service data.
service_data = {ATTR_ENTITY_ID: light}
features, supports_colors = _supported_features(self.hass, light)
features = _supported_features(self.hass, light)

# Check transition == 0 to fix #378
use_transition = ATTR_TRANSITION in features and transition > 0
use_transition = "transition" in features and transition > 0
if use_transition:
service_data[ATTR_TRANSITION] = transition
if ATTR_BRIGHTNESS in features and adapt_brightness:
if "brightness" in features and adapt_brightness:
brightness = round(255 * self._settings["brightness_pct"] / 100)
service_data[ATTR_BRIGHTNESS] = brightness

Expand All @@ -1137,18 +1103,19 @@ async def prepare_adaptation_data(
and self._sun_light_settings.sleep_rgb_or_color_temp == "rgb_color"
)
if (
ATTR_COLOR_TEMP_KELVIN in features
"color_temp" in features
and adapt_color
and not (prefer_rgb_color and supports_colors)
and not (sleep_rgb and supports_colors)
and not (prefer_rgb_color and "color" in features)
and not (sleep_rgb and "color" in features)
):
_LOGGER.debug("%s: Setting color_temp of light %s", self._name, light)
min_kelvin = features[ATTR_MIN_COLOR_TEMP_KELVIN]
max_kelvin = features[ATTR_MAX_COLOR_TEMP_KELVIN]
attributes = self.hass.states.get(light).attributes
min_kelvin = attributes["min_color_temp_kelvin"]
max_kelvin = attributes["max_color_temp_kelvin"]
color_temp_kelvin = self._settings["color_temp_kelvin"]
color_temp_kelvin = max(min(color_temp_kelvin, max_kelvin), min_kelvin)
service_data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin
elif supports_colors and adapt_color:
elif "color" in features and adapt_color:
_LOGGER.debug("%s: Setting rgb_color of light %s", self._name, light)
service_data[ATTR_RGB_COLOR] = self._settings["rgb_color"]

Expand Down
84 changes: 1 addition & 83 deletions tests/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,18 @@
import asyncio
from copy import deepcopy
import datetime
import itertools
import logging
from random import randint
from typing import Any
from unittest.mock import MagicMock, Mock, patch
from unittest.mock import Mock, patch

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT,
ATTR_COLOR_TEMP_KELVIN,
ATTR_MAX_COLOR_TEMP_KELVIN,
ATTR_MIN_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION,
ATTR_XY_COLOR,
COLOR_MODE_BRIGHTNESS,
)
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.light import SERVICE_TURN_OFF
Expand Down Expand Up @@ -75,7 +70,6 @@
CONF_TRANSITION,
CONF_TURN_ON_LIGHTS,
CONF_USE_DEFAULTS,
CONST_COLOR,
DEFAULT_MAX_BRIGHTNESS,
DEFAULT_NAME,
DEFAULT_SLEEP_BRIGHTNESS,
Expand All @@ -88,12 +82,9 @@
UNDO_UPDATE_LISTENER,
)
from custom_components.adaptive_lighting.switch import (
_SUPPORT_OPTS,
INTERNAL_CONF_PROACTIVE_SERVICE_CALL_ADAPTATION,
VALID_COLOR_MODES,
AdaptiveSwitch,
_attributes_have_changed,
_supported_features,
color_difference_redmean,
create_context,
is_our_context,
Expand Down Expand Up @@ -559,79 +550,6 @@ async def test_turn_on_off_listener_not_tracking_untracked_lights(hass):
assert light not in switch.turn_on_off_listener.lights


def test_supported_features(hass): # noqa: C901
"""Test the supported features of a light."""

possible_legacy_features = {}
MAX_COMBINATIONS = 4 # maximum number of elements that can be combined
for i in range(1, min(MAX_COMBINATIONS, len(_SUPPORT_OPTS)) + 1):
for combination in itertools.combinations(_SUPPORT_OPTS.keys(), i):
key = "_".join(combination)
value = [v for k, v in _SUPPORT_OPTS.items() if k in combination]
possible_legacy_features[key] = value

possible_color_modes = {}
for i in range(1, len(VALID_COLOR_MODES) + 1):
for combination in itertools.combinations(VALID_COLOR_MODES.keys(), i):
key = "_".join(combination)
value = [v for k, v in VALID_COLOR_MODES.items() if k in combination]
possible_color_modes[key] = value

# create a mock HomeAssistant object
hass = MagicMock()

# iterate over possible legacy features
for feature_key, feature_values in possible_legacy_features.items():
# _LOGGER.debug(feature_values)
# set the attributes of the mock state object to the possible legacy feature values
state_attrs = {ATTR_SUPPORTED_FEATURES: sum(feature_values)}
hass.states.get.return_value.attributes = state_attrs

# iterate over possible color modes
for mode_key, mode_values in possible_color_modes.items():
# _LOGGER.debug(mode_values)
# set the attributes of the mock state object to the possible color mode values
state_attrs[ATTR_SUPPORTED_COLOR_MODES] = set(mode_values)
hass.states.get.return_value.attributes = state_attrs

# Handle both the new and the old _supported_features.
result = _supported_features(hass, ENTITY_LIGHT)
supported, supports_colors = (
result if isinstance(result, tuple) else (result, None)
)
expected_supported = {} if supports_colors is not None else set()
for mode, attr in VALID_COLOR_MODES.items():
if mode in mode_values:
if supports_colors is None:
expected_supported.add(mode)
else:
expected_supported[attr] = True
if supports_colors is True:
expected_supported[COLOR_MODE_BRIGHTNESS] = True
for opt, value in _SUPPORT_OPTS.items():
if value in feature_values:
if supports_colors is None:
expected_supported.add(opt)
else:
if supports_colors is True:
expected_supported[COLOR_MODE_BRIGHTNESS] = True
if opt in VALID_COLOR_MODES:
expected_supported[VALID_COLOR_MODES[opt]] = True
elif opt != CONST_COLOR:
expected_supported[opt] = True
if ATTR_MIN_COLOR_TEMP_KELVIN in supported:
supported.pop(ATTR_MIN_COLOR_TEMP_KELVIN)
if ATTR_MAX_COLOR_TEMP_KELVIN in supported:
supported.pop(ATTR_MAX_COLOR_TEMP_KELVIN)
assert supported == expected_supported, (
f"\nExpected supported: {expected_supported}\n"
f"Actual supported: {supported}\n"
f"feature_values: {feature_values}\n"
f"mode_values: {mode_values}\n"
f"supports_colors: {supports_colors}\n"
)


@pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES)
async def test_manual_control(hass):
"""Test the 'manual control' tracking."""
Expand Down

0 comments on commit 19d503b

Please sign in to comment.