mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-12 06:44:04 +02:00
parent
fd5f6bf310
commit
19d503b4d7
3 changed files with 39 additions and 156 deletions
|
|
@ -289,8 +289,6 @@ VALIDATION_TUPLES = [
|
|||
),
|
||||
]
|
||||
|
||||
CONST_COLOR = "color"
|
||||
|
||||
|
||||
def timedelta_as_int(value):
|
||||
"""Convert a `datetime.timedelta` object to an integer.
|
||||
|
|
|
|||
|
|
@ -18,12 +18,7 @@ import astral
|
|||
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,
|
||||
|
|
@ -141,7 +136,6 @@ from .const import (
|
|||
CONF_TRANSITION,
|
||||
CONF_TURN_ON_LIGHTS,
|
||||
CONF_USE_DEFAULTS,
|
||||
CONST_COLOR,
|
||||
DOMAIN,
|
||||
EXTRA_VALIDATION,
|
||||
ICON_BRIGHTNESS,
|
||||
|
|
@ -163,21 +157,10 @@ from .const import (
|
|||
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)
|
||||
|
|
@ -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(
|
||||
|
|
@ -1122,13 +1088,13 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
|
||||
# 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
|
||||
|
||||
|
|
@ -1137,18 +1103,19 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -75,7 +70,6 @@ from custom_components.adaptive_lighting.const import (
|
|||
CONF_TRANSITION,
|
||||
CONF_TURN_ON_LIGHTS,
|
||||
CONF_USE_DEFAULTS,
|
||||
CONST_COLOR,
|
||||
DEFAULT_MAX_BRIGHTNESS,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_SLEEP_BRIGHTNESS,
|
||||
|
|
@ -88,12 +82,9 @@ from custom_components.adaptive_lighting.const import (
|
|||
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,
|
||||
|
|
@ -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."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue