mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-14 15:54:04 +02:00
Compare commits
12 commits
main
...
adaptive-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdedba9b40 | ||
|
|
1a5cf9d410 |
||
|
|
9a2943db50 | ||
|
|
b87ee215b3 | ||
|
|
77812de08c | ||
|
|
ea2e533186 | ||
|
|
9bec45b63c | ||
|
|
ce4e04358e | ||
|
|
f0b487320d | ||
|
|
d1a7b9f970 | ||
|
|
7895075e76 | ||
|
|
b6790b14eb |
7 changed files with 766 additions and 333 deletions
|
|
@ -34,7 +34,7 @@ runs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in pytest.yaml. ###"
|
echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in '\workflows\install_dependencies\action.yml'. ###"
|
||||||
pip install -r core/requirements.txt --use-pep517
|
pip install -r core/requirements.txt --use-pep517
|
||||||
# because they decided to pull codecov the package from PyPI...
|
# because they decided to pull codecov the package from PyPI...
|
||||||
sed -i '/codecov/d' core/requirements_test.txt
|
sed -i '/codecov/d' core/requirements_test.txt
|
||||||
|
|
|
||||||
64
README.md
64
README.md
|
|
@ -90,37 +90,39 @@ The YAML and frontend configuration methods support all of the options listed be
|
||||||
|
|
||||||
<!-- START_OUTPUT -->
|
<!-- START_OUTPUT -->
|
||||||
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
|
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
|
||||||
| Variable name | Description | Default | Type |
|
| Variable name | Description | Default | Type |
|
||||||
|:-------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------------------------|
|
|:-------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------------------------|
|
||||||
| `lights` | List of light entity_ids to be controlled (may be empty). 🌟 | `[]` | list of `entity_id`s |
|
| `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` |
|
| `watched_lights` | Use this dictionary of lights to check for manually controlled events in addition to the main lights 🌟 Example: {light.watch_light: light.main_light} will fire manually controlled events to light.main_light | `{}` | list of `entity_id`s |
|
||||||
| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` |
|
| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | `False` | `bool` |
|
||||||
| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 |
|
| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` |
|
||||||
| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 |
|
| `initial_transition` | Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️ | `1` | `float` 0-6553 |
|
||||||
| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 |
|
| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `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` |
|
| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 |
|
||||||
| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` |
|
| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` |
|
||||||
| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 |
|
| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` |
|
||||||
| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 |
|
| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 |
|
||||||
| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 |
|
| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 |
|
||||||
| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 |
|
| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 |
|
||||||
| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 |
|
| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 |
|
||||||
| `sleep_rgb_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` |
|
| `sleep_brightness` | Brightness percentage of lights in sleep mode. 😴 | `1` | `int` 1-100 |
|
||||||
| `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_or_color_temp` | Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` |
|
||||||
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | 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 |
|
||||||
| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` |
|
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color |
|
||||||
| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅 | `None` | `str` |
|
| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` |
|
||||||
| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
|
| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅 | `None` | `str` |
|
||||||
| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` |
|
| `sunrise_offset` | Adjust sunrise time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
|
||||||
| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇 | `None` | `str` |
|
| `sunset_time` | Set a fixed time (HH:MM:SS) for sunset. 🌇 | `None` | `str` |
|
||||||
| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
|
| `min_sunset_time` | Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇 | `None` | `str` |
|
||||||
| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` |
|
| `sunset_offset` | Adjust sunset time with a positive or negative offset in seconds. ⏰ | `0` | `int` |
|
||||||
| `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` |
|
| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` |
|
||||||
| `detect_non_ha_changes` | Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️ | `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` |
|
||||||
| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` |
|
| `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) | `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 |
|
| `detect_non_ha_changes` | Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️ | `False` | `bool` |
|
||||||
| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️ | `0` | `float > 0` |
|
| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` |
|
||||||
| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-31536000 |
|
| `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 |
|
||||||
|
|
||||||
<!-- END_OUTPUT -->
|
<!-- END_OUTPUT -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
"""Constants for the Adaptive Lighting integration."""
|
"""Constants for the Adaptive Lighting integration."""
|
||||||
|
|
||||||
from homeassistant.components.light import VALID_TRANSITION
|
from homeassistant.components.light import VALID_TRANSITION
|
||||||
from homeassistant.const import CONF_ENTITY_ID
|
from homeassistant.const import (
|
||||||
|
CONF_ENTITY_ID,
|
||||||
|
SERVICE_TOGGLE,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
)
|
||||||
from homeassistant.helpers import selector
|
from homeassistant.helpers import selector
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
@ -33,6 +38,12 @@ DOCS[CONF_DETECT_NON_HA_CHANGES] = (
|
||||||
"Requires `take_over_control`. 🕵️"
|
"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 = (
|
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES = (
|
||||||
"include_config_in_attributes",
|
"include_config_in_attributes",
|
||||||
False,
|
False,
|
||||||
|
|
@ -68,6 +79,14 @@ DOCS[CONF_MIN_BRIGHTNESS] = "Minimum brightness percentage. 💡"
|
||||||
CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2000
|
CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2000
|
||||||
DOCS[CONF_MIN_COLOR_TEMP] = "Warmest color temperature in Kelvin. 🔥"
|
DOCS[CONF_MIN_COLOR_TEMP] = "Warmest color temperature in Kelvin. 🔥"
|
||||||
|
|
||||||
|
CONF_FLAT_LIMITS, DEFAULT_FLAT_LIMITS = "flat_limits", False
|
||||||
|
DOCS[CONF_FLAT_LIMITS] = (
|
||||||
|
"When True, will not calculate between the"
|
||||||
|
" max/min supported limits of your light. Example: when adapting brightness to 50% while "
|
||||||
|
+ CONF_MAX_BRIGHTNESS
|
||||||
|
+ " is set to 80%, Adaptive Lighting will use 80% instead of 90%"
|
||||||
|
)
|
||||||
|
|
||||||
CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE = "only_once", False
|
CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE = "only_once", False
|
||||||
DOCS[CONF_ONLY_ONCE] = (
|
DOCS[CONF_ONLY_ONCE] = (
|
||||||
"Adapt lights only when they are turned on (`true`) or keep adapting them "
|
"Adapt lights only when they are turned on (`true`) or keep adapting them "
|
||||||
|
|
@ -154,9 +173,24 @@ CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP = (
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
DOCS[CONF_ADAPT_UNTIL_SLEEP] = (
|
DOCS[CONF_ADAPT_UNTIL_SLEEP] = (
|
||||||
|
"This option ignores the current state of the sleep switch. "
|
||||||
"When enabled, Adaptive Lighting will treat sleep settings as the minimum, "
|
"When enabled, Adaptive Lighting will treat sleep settings as the minimum, "
|
||||||
"transitioning to these values after sunset. 🌙"
|
"transitioning color temperature to these values after sunset. 🌙"
|
||||||
)
|
)
|
||||||
|
CONF_ADAPT_COLOR_TEMP_UNTIL_SLEEP, DEFAULT_ADAPT_COLOR_TEMP_UNTIL_SLEEP = (
|
||||||
|
"adapt_color_temp_until_sleep",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
DOCS[
|
||||||
|
CONF_ADAPT_COLOR_TEMP_UNTIL_SLEEP
|
||||||
|
] = "Only active when `transition_until_sleep` is true."
|
||||||
|
CONF_ADAPT_BRIGHTNESS_UNTIL_SLEEP, DEFAULT_ADAPT_BRIGHTNESS_UNTIL_SLEEP = (
|
||||||
|
"adapt_brightness_until_sleep",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
DOCS[
|
||||||
|
CONF_ADAPT_BRIGHTNESS_UNTIL_SLEEP
|
||||||
|
] = "Only active when `transition_until_sleep` is true."
|
||||||
|
|
||||||
CONF_ADAPT_DELAY, DEFAULT_ADAPT_DELAY = "adapt_delay", 0
|
CONF_ADAPT_DELAY, DEFAULT_ADAPT_DELAY = "adapt_delay", 0
|
||||||
DOCS[CONF_ADAPT_DELAY] = (
|
DOCS[CONF_ADAPT_DELAY] = (
|
||||||
|
|
@ -176,6 +210,15 @@ DOCS[CONF_AUTORESET_CONTROL] = (
|
||||||
"Set to 0 to disable. ⏲️"
|
"Set to 0 to disable. ⏲️"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_WATCHED_LIGHTS, DEFAULT_WATCHED_LIGHTS = "watched_lights", {}
|
||||||
|
DOCS[CONF_WATCHED_LIGHTS] = (
|
||||||
|
"Use this dictionary of lights to check for manually controlled events"
|
||||||
|
" in addition to the main lights 🌟"
|
||||||
|
" Requires `alt_detect_method` to be True."
|
||||||
|
" Example: `watched_lights: {light.watch_light: light.main_light}` will fire"
|
||||||
|
" manually controlled events to `light.main_light`"
|
||||||
|
)
|
||||||
|
|
||||||
SLEEP_MODE_SWITCH = "sleep_mode_switch"
|
SLEEP_MODE_SWITCH = "sleep_mode_switch"
|
||||||
ADAPT_COLOR_SWITCH = "adapt_color_switch"
|
ADAPT_COLOR_SWITCH = "adapt_color_switch"
|
||||||
ADAPT_BRIGHTNESS_SWITCH = "adapt_brightness_switch"
|
ADAPT_BRIGHTNESS_SWITCH = "adapt_brightness_switch"
|
||||||
|
|
@ -196,11 +239,24 @@ DOCS[CONF_TURN_ON_LIGHTS] = "Whether to turn on lights that are currently off.
|
||||||
SERVICE_CHANGE_SWITCH_SETTINGS = "change_switch_settings"
|
SERVICE_CHANGE_SWITCH_SETTINGS = "change_switch_settings"
|
||||||
CONF_USE_DEFAULTS = "use_defaults"
|
CONF_USE_DEFAULTS = "use_defaults"
|
||||||
DOCS[CONF_USE_DEFAULTS] = (
|
DOCS[CONF_USE_DEFAULTS] = (
|
||||||
"Sets the default values not specified in this service call. Options: "
|
"Where to autofill config options that are not passed to this service. Options: "
|
||||||
'"current" (default, retains current values), "factory" (resets to '
|
'"current" (default, retains current values), "factory" (resets to '
|
||||||
'documented defaults), or "configuration" (reverts to switch config defaults). ⚙️'
|
'documented defaults), or "configuration" (reverts to original user config). ⚙️'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_WHICH_SWITCH, DEFAULT_WHICH_SWITCH = "switch_type", "main"
|
||||||
|
DOCS[CONF_WHICH_SWITCH] = (
|
||||||
|
"Which switch to target in this service call. Options: "
|
||||||
|
'"main" (default, targets the main switch), "sleep", "brightness", "color"'
|
||||||
|
)
|
||||||
|
DOCS[
|
||||||
|
SERVICE_TURN_ON
|
||||||
|
] = "Turn on an Adaptive Lighting main/sleep/brightness/color switch"
|
||||||
|
DOCS[
|
||||||
|
SERVICE_TURN_OFF
|
||||||
|
] = "Turn off an Adaptive Lighting main/sleep/brightness/color switch"
|
||||||
|
DOCS[SERVICE_TOGGLE] = "Toggle an Adaptive Lighting main/sleep/brightness/color switch"
|
||||||
|
|
||||||
TURNING_OFF_DELAY = 5
|
TURNING_OFF_DELAY = 5
|
||||||
|
|
||||||
DOCS_MANUAL_CONTROL = {
|
DOCS_MANUAL_CONTROL = {
|
||||||
|
|
@ -225,12 +281,15 @@ def int_between(min_int, max_int):
|
||||||
|
|
||||||
VALIDATION_TUPLES = [
|
VALIDATION_TUPLES = [
|
||||||
(CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids),
|
(CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids),
|
||||||
|
(CONF_WATCHED_LIGHTS, DEFAULT_WATCHED_LIGHTS, dict),
|
||||||
(CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool),
|
(CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool),
|
||||||
(CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES, bool),
|
(CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES, bool),
|
||||||
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
|
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
|
||||||
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
|
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
|
||||||
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
|
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
|
||||||
(CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP, bool),
|
(CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP, bool),
|
||||||
|
(CONF_ADAPT_BRIGHTNESS_UNTIL_SLEEP, DEFAULT_ADAPT_BRIGHTNESS_UNTIL_SLEEP, bool),
|
||||||
|
(CONF_ADAPT_COLOR_TEMP_UNTIL_SLEEP, DEFAULT_ADAPT_COLOR_TEMP_UNTIL_SLEEP, bool),
|
||||||
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
|
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
|
||||||
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
|
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
|
||||||
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
|
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
|
||||||
|
|
@ -261,7 +320,9 @@ VALIDATION_TUPLES = [
|
||||||
(CONF_MIN_SUNSET_TIME, NONE_STR, str),
|
(CONF_MIN_SUNSET_TIME, NONE_STR, str),
|
||||||
(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int),
|
(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int),
|
||||||
(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE, bool),
|
(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE, bool),
|
||||||
|
(CONF_FLAT_LIMITS, DEFAULT_FLAT_LIMITS, bool),
|
||||||
(CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL, 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_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES, bool),
|
||||||
(CONF_SEPARATE_TURN_ON_COMMANDS, DEFAULT_SEPARATE_TURN_ON_COMMANDS, bool),
|
(CONF_SEPARATE_TURN_ON_COMMANDS, DEFAULT_SEPARATE_TURN_ON_COMMANDS, bool),
|
||||||
(CONF_SEND_SPLIT_DELAY, DEFAULT_SEND_SPLIT_DELAY, int_between(0, 10000)),
|
(CONF_SEND_SPLIT_DELAY, DEFAULT_SEND_SPLIT_DELAY, int_between(0, 10000)),
|
||||||
|
|
@ -273,8 +334,6 @@ VALIDATION_TUPLES = [
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONST_COLOR = "color"
|
|
||||||
|
|
||||||
|
|
||||||
def timedelta_as_int(value):
|
def timedelta_as_int(value):
|
||||||
"""Convert a `datetime.timedelta` object to an integer.
|
"""Convert a `datetime.timedelta` object to an integer.
|
||||||
|
|
@ -323,28 +382,43 @@ _DOMAIN_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_service_schema(initial_transition: int = 1):
|
SCHEMA_APPLY = vol.Schema(
|
||||||
"""Return the schema for the apply service."""
|
{
|
||||||
return vol.Schema(
|
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||||
{
|
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
vol.Optional(CONF_TRANSITION): VALID_TRANSITION,
|
||||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean,
|
||||||
vol.Optional(
|
vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean,
|
||||||
CONF_TRANSITION,
|
vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean,
|
||||||
default=initial_transition,
|
vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean,
|
||||||
): VALID_TRANSITION,
|
}
|
||||||
vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean,
|
)
|
||||||
vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean,
|
|
||||||
vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean,
|
|
||||||
vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
SCHEMA_SERVICE_TOGGLE = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||||
|
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||||
|
vol.Optional(CONF_WHICH_SWITCH): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
SET_MANUAL_CONTROL_SCHEMA = vol.Schema(
|
SCHEMA_SET_MANUAL_CONTROL = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||||
vol.Optional(CONF_MANUAL_CONTROL, default=True): cv.boolean,
|
vol.Optional(CONF_MANUAL_CONTROL, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SCHEMA_CHANGE_SWITCH_SETTINGS = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_USE_DEFAULTS): cv.string,
|
||||||
|
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||||
|
vol.Required(CONF_LIGHTS, default=[]): [],
|
||||||
|
**{
|
||||||
|
vol.Optional(k): valid
|
||||||
|
for k, _, valid in VALIDATION_TUPLES
|
||||||
|
if k not in [CONF_INTERVAL, CONF_NAME, CONF_LIGHTS]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@
|
||||||
"description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have this entry defined in YAML.",
|
"description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have this entry defined in YAML.",
|
||||||
"data": {
|
"data": {
|
||||||
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
||||||
|
"watched_lights": "watched_lights: Use this dictionary of lights to check for manually controlled events in addition to the main lights 🌟 Example: {light.watch_light: light.main_light} will fire manually controlled events to light.main_light",
|
||||||
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
||||||
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
||||||
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
||||||
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||||
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
||||||
"transition_until_sleep": "transition_until_sleep: When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙",
|
"transition_until_sleep": "transition_until_sleep: This option ignores the current state of the sleep switch. When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning color temperature to these values after sunset. 🌙",
|
||||||
|
"adapt_brightness_until_sleep": "adapt_brightness_until_sleep: Only active when `transition_until_sleep` is true.",
|
||||||
|
"adapt_color_temp_until_sleep": "adapt_color_temp_until_sleep: Only active when `transition_until_sleep` is true.",
|
||||||
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
||||||
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
||||||
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
||||||
|
|
@ -42,7 +45,9 @@
|
||||||
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
||||||
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
|
"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`). 🔄",
|
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
|
||||||
|
"flat_limits": "flat_limits: When True, will not calculate between the max/min supported limits of your light. Example: when adapting brightness to 50% while max_brightness is set to 80%, Adaptive Lighting will use 80% instead of 90%",
|
||||||
"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`! 🔒",
|
"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`! 🔒",
|
||||||
|
"alt_detect_method": "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)",
|
||||||
"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: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
|
||||||
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
|
"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. ⏲️",
|
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,7 @@ from homeassistant.components.light import (
|
||||||
ATTR_COLOR_NAME,
|
ATTR_COLOR_NAME,
|
||||||
ATTR_COLOR_TEMP_KELVIN,
|
ATTR_COLOR_TEMP_KELVIN,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
ATTR_MAX_COLOR_TEMP_KELVIN,
|
|
||||||
ATTR_MIN_COLOR_TEMP_KELVIN,
|
|
||||||
ATTR_RGB_COLOR,
|
ATTR_RGB_COLOR,
|
||||||
ATTR_RGBW_COLOR,
|
|
||||||
ATTR_RGBWW_COLOR,
|
|
||||||
ATTR_SUPPORTED_COLOR_MODES,
|
ATTR_SUPPORTED_COLOR_MODES,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
ATTR_XY_COLOR,
|
ATTR_XY_COLOR,
|
||||||
|
|
@ -36,7 +32,6 @@ from homeassistant.components.light import (
|
||||||
COLOR_MODE_HS,
|
COLOR_MODE_HS,
|
||||||
COLOR_MODE_RGB,
|
COLOR_MODE_RGB,
|
||||||
COLOR_MODE_RGBW,
|
COLOR_MODE_RGBW,
|
||||||
COLOR_MODE_RGBWW,
|
|
||||||
COLOR_MODE_XY,
|
COLOR_MODE_XY,
|
||||||
)
|
)
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
|
@ -61,6 +56,7 @@ from homeassistant.const import (
|
||||||
EVENT_CALL_SERVICE,
|
EVENT_CALL_SERVICE,
|
||||||
EVENT_HOMEASSISTANT_STARTED,
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
|
SERVICE_TOGGLE,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
|
|
@ -74,9 +70,10 @@ from homeassistant.core import (
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
ServiceCall,
|
ServiceCall,
|
||||||
State,
|
State,
|
||||||
|
async_get_hass,
|
||||||
callback,
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import entity_platform, entity_registry
|
from homeassistant.helpers import entity_registry
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
|
|
@ -94,7 +91,6 @@ from homeassistant.util.color import (
|
||||||
)
|
)
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import ulid_transform
|
import ulid_transform
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ADAPT_BRIGHTNESS_SWITCH,
|
ADAPT_BRIGHTNESS_SWITCH,
|
||||||
|
|
@ -102,10 +98,14 @@ from .const import (
|
||||||
ATTR_ADAPT_BRIGHTNESS,
|
ATTR_ADAPT_BRIGHTNESS,
|
||||||
ATTR_ADAPT_COLOR,
|
ATTR_ADAPT_COLOR,
|
||||||
ATTR_TURN_ON_OFF_LISTENER,
|
ATTR_TURN_ON_OFF_LISTENER,
|
||||||
|
CONF_ADAPT_BRIGHTNESS_UNTIL_SLEEP,
|
||||||
|
CONF_ADAPT_COLOR_TEMP_UNTIL_SLEEP,
|
||||||
CONF_ADAPT_DELAY,
|
CONF_ADAPT_DELAY,
|
||||||
CONF_ADAPT_UNTIL_SLEEP,
|
CONF_ADAPT_UNTIL_SLEEP,
|
||||||
|
CONF_ALT_DETECT_METHOD,
|
||||||
CONF_AUTORESET_CONTROL,
|
CONF_AUTORESET_CONTROL,
|
||||||
CONF_DETECT_NON_HA_CHANGES,
|
CONF_DETECT_NON_HA_CHANGES,
|
||||||
|
CONF_FLAT_LIMITS,
|
||||||
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
|
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
|
||||||
CONF_INITIAL_TRANSITION,
|
CONF_INITIAL_TRANSITION,
|
||||||
CONF_INTERVAL,
|
CONF_INTERVAL,
|
||||||
|
|
@ -134,23 +134,26 @@ from .const import (
|
||||||
CONF_TRANSITION,
|
CONF_TRANSITION,
|
||||||
CONF_TURN_ON_LIGHTS,
|
CONF_TURN_ON_LIGHTS,
|
||||||
CONF_USE_DEFAULTS,
|
CONF_USE_DEFAULTS,
|
||||||
CONST_COLOR,
|
CONF_WATCHED_LIGHTS,
|
||||||
|
CONF_WHICH_SWITCH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EXTRA_VALIDATION,
|
EXTRA_VALIDATION,
|
||||||
ICON_BRIGHTNESS,
|
ICON_BRIGHTNESS,
|
||||||
ICON_COLOR_TEMP,
|
ICON_COLOR_TEMP,
|
||||||
ICON_MAIN,
|
ICON_MAIN,
|
||||||
ICON_SLEEP,
|
ICON_SLEEP,
|
||||||
|
SCHEMA_APPLY,
|
||||||
|
SCHEMA_CHANGE_SWITCH_SETTINGS,
|
||||||
|
SCHEMA_SERVICE_TOGGLE,
|
||||||
|
SCHEMA_SET_MANUAL_CONTROL,
|
||||||
SERVICE_APPLY,
|
SERVICE_APPLY,
|
||||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||||
SERVICE_SET_MANUAL_CONTROL,
|
SERVICE_SET_MANUAL_CONTROL,
|
||||||
SET_MANUAL_CONTROL_SCHEMA,
|
|
||||||
SLEEP_MODE_SWITCH,
|
SLEEP_MODE_SWITCH,
|
||||||
SUN_EVENT_MIDNIGHT,
|
SUN_EVENT_MIDNIGHT,
|
||||||
SUN_EVENT_NOON,
|
SUN_EVENT_NOON,
|
||||||
TURNING_OFF_DELAY,
|
TURNING_OFF_DELAY,
|
||||||
VALIDATION_TUPLES,
|
VALIDATION_TUPLES,
|
||||||
apply_service_schema,
|
|
||||||
replace_none_str,
|
replace_none_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -161,7 +164,6 @@ _SUPPORT_OPTS = {
|
||||||
ATTR_TRANSITION: SUPPORT_TRANSITION,
|
ATTR_TRANSITION: SUPPORT_TRANSITION,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
VALID_COLOR_MODES = {
|
VALID_COLOR_MODES = {
|
||||||
COLOR_MODE_BRIGHTNESS: ATTR_BRIGHTNESS,
|
COLOR_MODE_BRIGHTNESS: ATTR_BRIGHTNESS,
|
||||||
COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP_KELVIN,
|
COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP_KELVIN,
|
||||||
|
|
@ -303,9 +305,9 @@ def _split_service_data(service_data, adapt_brightness, adapt_color):
|
||||||
|
|
||||||
|
|
||||||
def _get_switches_with_lights(
|
def _get_switches_with_lights(
|
||||||
hass: HomeAssistant, lights: list[str]
|
hass: HomeAssistant, lights: list[str] | None = None
|
||||||
) -> list[AdaptiveSwitch]:
|
) -> list[AdaptiveSwitch]:
|
||||||
"""Get all switches that control at least one of the lights passed."""
|
"""Get all switches. If lights is defined, return only switches found with these lights."""
|
||||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
data = hass.data[DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
switches = []
|
switches = []
|
||||||
|
|
@ -314,10 +316,13 @@ def _get_switches_with_lights(
|
||||||
if entry is None: # entry might be disabled and therefore missing
|
if entry is None: # entry might be disabled and therefore missing
|
||||||
continue
|
continue
|
||||||
switch = data[config.entry_id]["instance"]
|
switch = data[config.entry_id]["instance"]
|
||||||
all_check_lights = _expand_light_groups(hass, lights)
|
if lights:
|
||||||
switch._expand_light_groups()
|
all_check_lights = _expand_light_groups(hass, lights)
|
||||||
# Check if any of the lights are in the switch's lights
|
switch._expand_light_groups()
|
||||||
if set(switch._lights) & set(all_check_lights):
|
# Check if any of the lights are in the switch's lights
|
||||||
|
if set(switch._lights) & set(all_check_lights):
|
||||||
|
switches.append(switch)
|
||||||
|
else:
|
||||||
switches.append(switch)
|
switches.append(switch)
|
||||||
return switches
|
return switches
|
||||||
|
|
||||||
|
|
@ -359,13 +364,7 @@ def _get_switches_from_service_call(
|
||||||
switch_entity_ids: list[str] | None = data.get("entity_id")
|
switch_entity_ids: list[str] | None = data.get("entity_id")
|
||||||
|
|
||||||
if not lights and not switch_entity_ids:
|
if not lights and not switch_entity_ids:
|
||||||
raise ValueError(
|
return _get_switches_with_lights(hass)
|
||||||
"adaptive-lighting: Neither a switch nor a light was provided in the service call."
|
|
||||||
" If you intend to adapt all lights on all switches, please inform the developers at"
|
|
||||||
" https://github.com/basnijholt/adaptive-lighting about your use case."
|
|
||||||
" Currently, you must pass either an adaptive-lighting switch or the lights to an"
|
|
||||||
" `adaptive_lighting` service call."
|
|
||||||
)
|
|
||||||
|
|
||||||
if switch_entity_ids is not None:
|
if switch_entity_ids is not None:
|
||||||
if len(switch_entity_ids) > 1 and lights:
|
if len(switch_entity_ids) > 1 and lights:
|
||||||
|
|
@ -391,44 +390,189 @@ def _get_switches_from_service_call(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_change_switch_settings(
|
@callback
|
||||||
switch: AdaptiveSwitch, service_call: ServiceCall
|
async def handle_apply(service_call: ServiceCall):
|
||||||
) -> None:
|
"""Handle the entity service apply."""
|
||||||
"""Allows HASS to change config values via a service call."""
|
hass = async_get_hass()
|
||||||
data = service_call.data
|
data = service_call.data
|
||||||
|
_LOGGER.debug(
|
||||||
which = data.get(CONF_USE_DEFAULTS, "current")
|
"Called 'adaptive_lighting.apply' service with '%s'",
|
||||||
if which == "current": # use whatever we're already using.
|
data,
|
||||||
defaults = switch._current_settings # pylint: disable=protected-access
|
|
||||||
elif which == "factory": # use actual defaults listed in the documentation
|
|
||||||
defaults = {key: default for key, default, _ in VALIDATION_TUPLES}
|
|
||||||
elif which == "configuration":
|
|
||||||
# use whatever's in the config flow or configuration.yaml
|
|
||||||
defaults = switch._config_backup # pylint: disable=protected-access
|
|
||||||
else:
|
|
||||||
defaults = None
|
|
||||||
|
|
||||||
switch._set_changeable_settings(
|
|
||||||
data=data,
|
|
||||||
defaults=defaults,
|
|
||||||
)
|
)
|
||||||
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
|
lights = data[CONF_LIGHTS]
|
||||||
|
for switch in switches:
|
||||||
|
if not lights:
|
||||||
|
all_lights = switch._lights # pylint: disable=protected-access
|
||||||
|
else:
|
||||||
|
all_lights = _expand_light_groups(switch.hass, lights)
|
||||||
|
switch.turn_on_off_listener.lights.update(all_lights)
|
||||||
|
for light in all_lights:
|
||||||
|
transition = data.get(CONF_TRANSITION)
|
||||||
|
if not data[CONF_TURN_ON_LIGHTS]:
|
||||||
|
if not is_on(hass, light):
|
||||||
|
continue
|
||||||
|
if not transition:
|
||||||
|
transition = switch._transition # pylint: disable=protected-access
|
||||||
|
elif not transition:
|
||||||
|
transition = (
|
||||||
|
switch._initial_transition
|
||||||
|
) # pylint: disable=protected-access
|
||||||
|
await switch._adapt_light( # pylint: disable=protected-access
|
||||||
|
light,
|
||||||
|
transition,
|
||||||
|
data[ATTR_ADAPT_BRIGHTNESS],
|
||||||
|
data[ATTR_ADAPT_COLOR],
|
||||||
|
data[CONF_PREFER_RGB_COLOR],
|
||||||
|
force=True,
|
||||||
|
context=switch.create_context("service", parent=service_call.context),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def handle_set_manual_control(service_call: ServiceCall):
|
||||||
|
"""Set or unset lights as 'manually controlled'."""
|
||||||
|
hass = async_get_hass()
|
||||||
|
data = service_call.data
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Called 'adaptive_lighting.set_manual_control' service with '%s'",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
|
lights = data[CONF_LIGHTS]
|
||||||
|
for switch in switches:
|
||||||
|
if not lights:
|
||||||
|
all_lights = switch._lights # pylint: disable=protected-access
|
||||||
|
else:
|
||||||
|
all_lights = _expand_light_groups(switch.hass, lights)
|
||||||
|
if service_call.data[CONF_MANUAL_CONTROL]:
|
||||||
|
for light in all_lights:
|
||||||
|
_fire_manual_control_event(switch, light, service_call.context)
|
||||||
|
else:
|
||||||
|
switch.turn_on_off_listener.reset(*all_lights)
|
||||||
|
if switch.is_on:
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
await switch._update_attrs_and_maybe_adapt_lights(
|
||||||
|
all_lights,
|
||||||
|
transition=switch._initial_transition,
|
||||||
|
force=True,
|
||||||
|
context=switch.create_context(
|
||||||
|
"service", parent=service_call.context
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def handle_change_switch_settings(service_call: ServiceCall) -> None:
|
||||||
|
"""Allows HASS to change config values via a service call."""
|
||||||
|
hass = async_get_hass()
|
||||||
|
data = service_call.data
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Called 'adaptive_lighting.change_switch_settings' service with '%s'",
|
"Called 'adaptive_lighting.change_switch_settings' service with '%s'",
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
|
|
||||||
all_lights = switch._lights # pylint: disable=protected-access
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False)
|
for switch in switches:
|
||||||
if switch.is_on:
|
# which denotes where to autofill blank config options.
|
||||||
|
which = data.get(CONF_USE_DEFAULTS, "current")
|
||||||
|
if which == "current":
|
||||||
|
# use whatever we're already using.
|
||||||
|
defaults = switch._current_settings # pylint: disable=protected-access
|
||||||
|
elif which == "factory":
|
||||||
|
# use actual defaults listed in the documentation
|
||||||
|
defaults = {key: default for key, default, _ in VALIDATION_TUPLES}
|
||||||
|
elif which == "configuration":
|
||||||
|
# use whatever's in the config flow or configuration.yaml
|
||||||
|
defaults = switch._config_backup # pylint: disable=protected-access
|
||||||
|
else:
|
||||||
|
defaults = None
|
||||||
|
|
||||||
|
switch._set_changeable_settings(
|
||||||
|
data=data,
|
||||||
|
defaults=defaults,
|
||||||
|
)
|
||||||
|
|
||||||
|
all_lights = switch._lights # pylint: disable=protected-access
|
||||||
|
switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False)
|
||||||
|
|
||||||
|
if not switch.is_on:
|
||||||
|
continue
|
||||||
await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access
|
await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access
|
||||||
all_lights,
|
all_lights,
|
||||||
transition=switch._initial_transition,
|
transition=switch._transition,
|
||||||
force=True,
|
force=True,
|
||||||
context=switch.create_context("service", parent=service_call.context),
|
context=switch.create_context("service", parent=service_call.context),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def handle_turn_on(service_call: ServiceCall):
|
||||||
|
"""Toggles the specified switch."""
|
||||||
|
hass = async_get_hass()
|
||||||
|
data = service_call.data
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Called 'adaptive_lighting.turn_on' service with '%s'",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
|
if data[CONF_WHICH_SWITCH] == "sleep":
|
||||||
|
switches = [s.sleep_mode_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "brightness":
|
||||||
|
switches = [s.adapt_brightness_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "color":
|
||||||
|
switches = [s.adapt_color_switch for s in switches]
|
||||||
|
|
||||||
|
_LOGGER.debug("Turning on switches [%s]", switches)
|
||||||
|
for switch in switches:
|
||||||
|
await switch.async_turn_on()
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def handle_turn_off(service_call: ServiceCall):
|
||||||
|
"""Toggles the specified switch."""
|
||||||
|
hass = async_get_hass()
|
||||||
|
data = service_call.data
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Called 'adaptive_lighting.turn_off' service with '%s'",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
|
if data[CONF_WHICH_SWITCH] == "sleep":
|
||||||
|
switches = [s.sleep_mode_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "brightness":
|
||||||
|
switches = [s.adapt_brightness_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "color":
|
||||||
|
switches = [s.adapt_color_switch for s in switches]
|
||||||
|
_LOGGER.debug("Turning off switches [%s]", switches)
|
||||||
|
for switch in switches:
|
||||||
|
await switch.async_turn_off()
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def handle_toggle(service_call: ServiceCall):
|
||||||
|
"""Toggles the specified switch."""
|
||||||
|
hass = async_get_hass()
|
||||||
|
data = service_call.data
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Called 'adaptive_lighting.toggle' service with '%s'",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
switches = _get_switches_from_service_call(hass, service_call)
|
||||||
|
if data[CONF_WHICH_SWITCH] == "sleep":
|
||||||
|
switches = [s.sleep_mode_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "brightness":
|
||||||
|
switches = [s.adapt_brightness_switch for s in switches]
|
||||||
|
elif data[CONF_WHICH_SWITCH] == "color":
|
||||||
|
switches = [s.adapt_color_switch for s in switches]
|
||||||
|
_LOGGER.debug("Toggling switches [%s]", switches)
|
||||||
|
for switch in switches:
|
||||||
|
if switch.is_on:
|
||||||
|
await switch.async_turn_off()
|
||||||
|
else:
|
||||||
|
await switch.async_turn_on()
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _fire_manual_control_event(
|
def _fire_manual_control_event(
|
||||||
switch: AdaptiveSwitch, light: str, context: Context, is_async=True
|
switch: AdaptiveSwitch, light: str, context: Context, is_async=True
|
||||||
|
|
@ -490,75 +634,36 @@ async def async_setup_entry(
|
||||||
update_before_add=True,
|
update_before_add=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
|
||||||
async def handle_apply(service_call: ServiceCall):
|
|
||||||
"""Handle the entity service apply."""
|
|
||||||
data = service_call.data
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Called 'adaptive_lighting.apply' service with '%s'",
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
switches = _get_switches_from_service_call(hass, service_call)
|
|
||||||
lights = data[CONF_LIGHTS]
|
|
||||||
for switch in switches:
|
|
||||||
if not lights:
|
|
||||||
all_lights = switch._lights # pylint: disable=protected-access
|
|
||||||
else:
|
|
||||||
all_lights = _expand_light_groups(switch.hass, lights)
|
|
||||||
switch.turn_on_off_listener.lights.update(all_lights)
|
|
||||||
for light in all_lights:
|
|
||||||
if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light):
|
|
||||||
await switch._adapt_light( # pylint: disable=protected-access
|
|
||||||
light,
|
|
||||||
data[CONF_TRANSITION],
|
|
||||||
data[ATTR_ADAPT_BRIGHTNESS],
|
|
||||||
data[ATTR_ADAPT_COLOR],
|
|
||||||
data[CONF_PREFER_RGB_COLOR],
|
|
||||||
force=True,
|
|
||||||
context=switch.create_context(
|
|
||||||
"service", parent=service_call.context
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
async def handle_set_manual_control(service_call: ServiceCall):
|
|
||||||
"""Set or unset lights as 'manually controlled'."""
|
|
||||||
data = service_call.data
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Called 'adaptive_lighting.set_manual_control' service with '%s'",
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
switches = _get_switches_from_service_call(hass, service_call)
|
|
||||||
lights = data[CONF_LIGHTS]
|
|
||||||
for switch in switches:
|
|
||||||
if not lights:
|
|
||||||
all_lights = switch._lights # pylint: disable=protected-access
|
|
||||||
else:
|
|
||||||
all_lights = _expand_light_groups(switch.hass, lights)
|
|
||||||
if service_call.data[CONF_MANUAL_CONTROL]:
|
|
||||||
for light in all_lights:
|
|
||||||
_fire_manual_control_event(switch, light, service_call.context)
|
|
||||||
else:
|
|
||||||
switch.turn_on_off_listener.reset(*all_lights)
|
|
||||||
if switch.is_on:
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
await switch._update_attrs_and_maybe_adapt_lights(
|
|
||||||
all_lights,
|
|
||||||
transition=switch._initial_transition,
|
|
||||||
force=True,
|
|
||||||
context=switch.create_context(
|
|
||||||
"service", parent=service_call.context
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Register `apply` service
|
# Register `apply` service
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
service=SERVICE_APPLY,
|
service=SERVICE_APPLY,
|
||||||
service_func=handle_apply,
|
service_func=handle_apply,
|
||||||
schema=apply_service_schema(
|
schema=SCHEMA_APPLY,
|
||||||
switch._initial_transition
|
)
|
||||||
), # pylint: disable=protected-access
|
|
||||||
|
# Register `turn_on` service
|
||||||
|
hass.services.async_register(
|
||||||
|
domain=DOMAIN,
|
||||||
|
service=SERVICE_TURN_ON,
|
||||||
|
service_func=handle_turn_on,
|
||||||
|
schema=SCHEMA_SERVICE_TOGGLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register `turn_off` service
|
||||||
|
hass.services.async_register(
|
||||||
|
domain=DOMAIN,
|
||||||
|
service=SERVICE_TURN_OFF,
|
||||||
|
service_func=handle_turn_off,
|
||||||
|
schema=SCHEMA_SERVICE_TOGGLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register `toggle` service
|
||||||
|
hass.services.async_register(
|
||||||
|
domain=DOMAIN,
|
||||||
|
service=SERVICE_TOGGLE,
|
||||||
|
service_func=handle_toggle,
|
||||||
|
schema=SCHEMA_SERVICE_TOGGLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register `set_manual_control` service
|
# Register `set_manual_control` service
|
||||||
|
|
@ -566,20 +671,15 @@ async def async_setup_entry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
service=SERVICE_SET_MANUAL_CONTROL,
|
service=SERVICE_SET_MANUAL_CONTROL,
|
||||||
service_func=handle_set_manual_control,
|
service_func=handle_set_manual_control,
|
||||||
schema=SET_MANUAL_CONTROL_SCHEMA,
|
schema=SCHEMA_SET_MANUAL_CONTROL,
|
||||||
)
|
)
|
||||||
|
|
||||||
args = {vol.Optional(CONF_USE_DEFAULTS, default="current"): cv.string}
|
# Register `change_switch_settings` service
|
||||||
# Modifying these after init isn't possible
|
hass.services.async_register(
|
||||||
skip = (CONF_INTERVAL, CONF_NAME, CONF_LIGHTS)
|
domain=DOMAIN,
|
||||||
for k, _, valid in VALIDATION_TUPLES:
|
service=SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||||
if k not in skip:
|
service_func=handle_change_switch_settings,
|
||||||
args[vol.Optional(k)] = valid
|
schema=SCHEMA_CHANGE_SWITCH_SETTINGS,
|
||||||
platform = entity_platform.current_platform.get()
|
|
||||||
platform.async_register_entity_service(
|
|
||||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
|
||||||
args,
|
|
||||||
handle_change_switch_settings,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -658,37 +758,6 @@ def _supported_to_attributes(supported):
|
||||||
return supported_attributes, supports_colors
|
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_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:
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
def color_difference_redmean(
|
def color_difference_redmean(
|
||||||
rgb1: tuple[float, float, float], rgb2: tuple[float, float, float]
|
rgb1: tuple[float, float, float], rgb2: tuple[float, float, float]
|
||||||
) -> float:
|
) -> float:
|
||||||
|
|
@ -708,6 +777,15 @@ def color_difference_redmean(
|
||||||
return math.sqrt(red_term + green_term + blue_term)
|
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
|
# All comparisons should be done with RGB since
|
||||||
# converting anything to color temp is inaccurate.
|
# converting anything to color temp is inaccurate.
|
||||||
def _convert_attributes(attributes: dict[str, Any]) -> dict[str, Any]:
|
def _convert_attributes(attributes: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
|
@ -750,6 +828,7 @@ def _attributes_have_changed(
|
||||||
adapt_brightness: bool,
|
adapt_brightness: bool,
|
||||||
adapt_color: bool,
|
adapt_color: bool,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
last_adapt_attempt=None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if adapt_color:
|
if adapt_color:
|
||||||
old_attributes, new_attributes = _add_missing_attributes(
|
old_attributes, new_attributes = _add_missing_attributes(
|
||||||
|
|
@ -764,15 +843,33 @@ def _attributes_have_changed(
|
||||||
last_brightness = old_attributes[ATTR_BRIGHTNESS]
|
last_brightness = old_attributes[ATTR_BRIGHTNESS]
|
||||||
current_brightness = new_attributes[ATTR_BRIGHTNESS]
|
current_brightness = new_attributes[ATTR_BRIGHTNESS]
|
||||||
if abs(current_brightness - last_brightness) > BRIGHTNESS_CHANGE:
|
if abs(current_brightness - last_brightness) > BRIGHTNESS_CHANGE:
|
||||||
_LOGGER.debug(
|
if last_adapt_attempt:
|
||||||
"Brightness of '%s' significantly changed from %s to %s with"
|
changed = check_direction_change(
|
||||||
" context.id='%s'",
|
last_brightness,
|
||||||
light,
|
current_brightness,
|
||||||
last_brightness,
|
last_adapt_attempt[ATTR_BRIGHTNESS],
|
||||||
current_brightness,
|
)
|
||||||
context.id,
|
_LOGGER.debug(
|
||||||
)
|
"altdetect: Brightness of '%s' changed from %s to %s intended %s with"
|
||||||
return True
|
" 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 (
|
if (
|
||||||
adapt_color
|
adapt_color
|
||||||
|
|
@ -782,15 +879,33 @@ def _attributes_have_changed(
|
||||||
last_color_temp = old_attributes[ATTR_COLOR_TEMP_KELVIN]
|
last_color_temp = old_attributes[ATTR_COLOR_TEMP_KELVIN]
|
||||||
current_color_temp = new_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:
|
if abs(current_color_temp - last_color_temp) > COLOR_TEMP_CHANGE:
|
||||||
_LOGGER.debug(
|
if last_adapt_attempt:
|
||||||
"Color temperature of '%s' significantly changed from %s to %s with"
|
changed = check_direction_change(
|
||||||
" context.id='%s'",
|
last_color_temp,
|
||||||
light,
|
current_color_temp,
|
||||||
last_color_temp,
|
last_adapt_attempt[ATTR_COLOR_TEMP_KELVIN],
|
||||||
current_color_temp,
|
)
|
||||||
context.id,
|
_LOGGER.debug(
|
||||||
)
|
"altdetect: Color temperature of '%s' changed from %s to %s intended %s with"
|
||||||
return True
|
" 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 (
|
if (
|
||||||
adapt_color
|
adapt_color
|
||||||
|
|
@ -910,13 +1025,20 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
self._transition = data[CONF_TRANSITION]
|
self._transition = data[CONF_TRANSITION]
|
||||||
self._adapt_delay = data[CONF_ADAPT_DELAY]
|
self._adapt_delay = data[CONF_ADAPT_DELAY]
|
||||||
self._send_split_delay = data[CONF_SEND_SPLIT_DELAY]
|
self._send_split_delay = data[CONF_SEND_SPLIT_DELAY]
|
||||||
|
self._watched_lights = data[CONF_WATCHED_LIGHTS]
|
||||||
self._take_over_control = data[CONF_TAKE_OVER_CONTROL]
|
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]
|
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]:
|
if not data[CONF_TAKE_OVER_CONTROL] and (
|
||||||
_LOGGER.warning(
|
data[CONF_ALT_DETECT_METHOD] or data[CONF_DETECT_NON_HA_CHANGES]
|
||||||
"%s: Config mismatch: 'detect_non_ha_changes: true' "
|
):
|
||||||
"requires 'take_over_control' to be enabled. Adjusting config "
|
_LOGGER.warn(
|
||||||
"and continuing setup with `take_over_control: true`.",
|
"%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._name,
|
||||||
)
|
)
|
||||||
self._take_over_control = True
|
self._take_over_control = True
|
||||||
|
|
@ -933,7 +1055,10 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
self._sun_light_settings = SunLightSettings(
|
self._sun_light_settings = SunLightSettings(
|
||||||
name=self._name,
|
name=self._name,
|
||||||
astral_location=location,
|
astral_location=location,
|
||||||
|
adapt_brightness_until_sleep=data[CONF_ADAPT_BRIGHTNESS_UNTIL_SLEEP],
|
||||||
|
adapt_color_temp_until_sleep=data[CONF_ADAPT_COLOR_TEMP_UNTIL_SLEEP],
|
||||||
adapt_until_sleep=data[CONF_ADAPT_UNTIL_SLEEP],
|
adapt_until_sleep=data[CONF_ADAPT_UNTIL_SLEEP],
|
||||||
|
flat_limits=data[CONF_FLAT_LIMITS],
|
||||||
max_brightness=data[CONF_MAX_BRIGHTNESS],
|
max_brightness=data[CONF_MAX_BRIGHTNESS],
|
||||||
max_color_temp=data[CONF_MAX_COLOR_TEMP],
|
max_color_temp=data[CONF_MAX_COLOR_TEMP],
|
||||||
min_brightness=data[CONF_MIN_BRIGHTNESS],
|
min_brightness=data[CONF_MIN_BRIGHTNESS],
|
||||||
|
|
@ -1141,12 +1266,12 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
|
|
||||||
# Build service data.
|
# Build service data.
|
||||||
service_data = {ATTR_ENTITY_ID: light}
|
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
|
# Check transition == 0 to fix #378
|
||||||
if ATTR_TRANSITION in features and transition > 0:
|
if "transition" in features and transition > 0:
|
||||||
service_data[ATTR_TRANSITION] = 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)
|
brightness = round(255 * self._settings["brightness_pct"] / 100)
|
||||||
service_data[ATTR_BRIGHTNESS] = brightness
|
service_data[ATTR_BRIGHTNESS] = brightness
|
||||||
|
|
||||||
|
|
@ -1155,26 +1280,31 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
and self._sun_light_settings.sleep_rgb_or_color_temp == "rgb_color"
|
and self._sun_light_settings.sleep_rgb_or_color_temp == "rgb_color"
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
ATTR_COLOR_TEMP_KELVIN in features
|
"color_temp" in features
|
||||||
and adapt_color
|
and adapt_color
|
||||||
and not (prefer_rgb_color and supports_colors)
|
and not (prefer_rgb_color and "color" in features)
|
||||||
and not (sleep_rgb and supports_colors)
|
and not (sleep_rgb and "color" in features)
|
||||||
):
|
):
|
||||||
_LOGGER.debug("%s: Setting color_temp of light %s", self._name, light)
|
_LOGGER.debug("%s: Setting color_temp of light %s", self._name, light)
|
||||||
min_kelvin = features[ATTR_MIN_COLOR_TEMP_KELVIN]
|
attributes = self.hass.states.get(light).attributes
|
||||||
max_kelvin = features[ATTR_MAX_COLOR_TEMP_KELVIN]
|
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 = self._settings["color_temp_kelvin"]
|
||||||
color_temp_kelvin = max(min(color_temp_kelvin, max_kelvin), min_kelvin)
|
color_temp_kelvin = max(min(color_temp_kelvin, max_kelvin), min_kelvin)
|
||||||
service_data[ATTR_COLOR_TEMP_KELVIN] = color_temp_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)
|
_LOGGER.debug("%s: Setting rgb_color of light %s", self._name, light)
|
||||||
service_data[ATTR_RGB_COLOR] = self._settings["rgb_color"]
|
service_data[ATTR_RGB_COLOR] = self._settings["rgb_color"]
|
||||||
|
# Check if service data differs from the last. See #80.
|
||||||
context = context or self.create_context("adapt_lights")
|
listener = self.turn_on_off_listener
|
||||||
|
last_service_data = listener.last_service_data.get(light)
|
||||||
# See #80. Doesn't check if transitions differ but it does the job.
|
ignore_fields = {ATTR_TRANSITION}
|
||||||
last_service_data = self.turn_on_off_listener.last_service_data
|
if (
|
||||||
if not force and last_service_data.get(light) == service_data:
|
not force
|
||||||
|
and last_service_data
|
||||||
|
and {k for k, _ in last_service_data.items() ^ service_data.items()}
|
||||||
|
== ignore_fields
|
||||||
|
):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s: Cancelling adapt to light %s, there's no new values to set (context.id='%s')",
|
"%s: Cancelling adapt to light %s, there's no new values to set (context.id='%s')",
|
||||||
self._name,
|
self._name,
|
||||||
|
|
@ -1182,8 +1312,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
context.id,
|
context.id,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
listener.last_service_data[light] = service_data
|
||||||
self.turn_on_off_listener.last_service_data[light] = service_data
|
|
||||||
|
context = context or self.create_context("adapt_lights")
|
||||||
|
|
||||||
async def turn_on(service_data):
|
async def turn_on(service_data):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
|
@ -1270,6 +1401,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
transition: int | None,
|
transition: int | None,
|
||||||
force: bool,
|
force: bool,
|
||||||
context: Context | None,
|
context: Context | None,
|
||||||
|
adapt_brightness: bool | None = None,
|
||||||
|
adapt_color: bool | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert context is not None
|
assert context is not None
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
|
@ -1284,13 +1417,17 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
adapt_brightness = self.adapt_brightness_switch.is_on
|
adapt_brightness = self.adapt_brightness_switch.is_on
|
||||||
adapt_color = self.adapt_color_switch.is_on
|
adapt_color = self.adapt_color_switch.is_on
|
||||||
|
|
||||||
for light in lights:
|
all_lights = {k: k for k in lights}
|
||||||
if not is_on(self.hass, light):
|
all_lights.update(self._watched_lights)
|
||||||
|
|
||||||
|
for wlight, mlight in all_lights.items():
|
||||||
|
if not is_on(self.hass, mlight):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
manually_controlled = self.turn_on_off_listener.is_manually_controlled(
|
manually_controlled = self.turn_on_off_listener.is_manually_controlled(
|
||||||
self,
|
self,
|
||||||
light,
|
wlight,
|
||||||
|
mlight,
|
||||||
force,
|
force,
|
||||||
adapt_brightness,
|
adapt_brightness,
|
||||||
adapt_color,
|
adapt_color,
|
||||||
|
|
@ -1301,7 +1438,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
and not force
|
and not force
|
||||||
and await self.turn_on_off_listener.significant_change(
|
and await self.turn_on_off_listener.significant_change(
|
||||||
self,
|
self,
|
||||||
light,
|
wlight,
|
||||||
|
mlight,
|
||||||
adapt_brightness,
|
adapt_brightness,
|
||||||
adapt_color,
|
adapt_color,
|
||||||
context,
|
context,
|
||||||
|
|
@ -1313,13 +1451,15 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s: '%s' is being manually controlled, stop adapting, context.id=%s.",
|
"%s: '%s' is being manually controlled, stop adapting, context.id=%s.",
|
||||||
self._name,
|
self._name,
|
||||||
light,
|
mlight,
|
||||||
context.id,
|
context.id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_fire_manual_control_event(self, light, context)
|
_fire_manual_control_event(self, mlight, context)
|
||||||
else:
|
else:
|
||||||
await self._adapt_light(light, transition, force=force, context=context)
|
await self._adapt_light(
|
||||||
|
wlight, transition, force=force, context=context
|
||||||
|
)
|
||||||
|
|
||||||
async def _sleep_mode_switch_state_event(self, event: Event) -> None:
|
async def _sleep_mode_switch_state_event(self, event: Event) -> None:
|
||||||
if not match_switch_state_event(event, (STATE_ON, STATE_OFF)):
|
if not match_switch_state_event(event, (STATE_ON, STATE_OFF)):
|
||||||
|
|
@ -1472,7 +1612,10 @@ class SunLightSettings:
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
astral_location: astral.Location
|
astral_location: astral.Location
|
||||||
|
adapt_brightness_until_sleep: bool
|
||||||
|
adapt_color_temp_until_sleep: bool
|
||||||
adapt_until_sleep: bool
|
adapt_until_sleep: bool
|
||||||
|
flat_limits: bool
|
||||||
max_brightness: int
|
max_brightness: int
|
||||||
max_color_temp: int
|
max_color_temp: int
|
||||||
min_brightness: int
|
min_brightness: int
|
||||||
|
|
@ -1507,15 +1650,27 @@ class SunLightSettings:
|
||||||
def calculate_noon_and_midnight(
|
def calculate_noon_and_midnight(
|
||||||
sunset: datetime.datetime, sunrise: datetime.datetime
|
sunset: datetime.datetime, sunrise: datetime.datetime
|
||||||
) -> tuple[datetime.datetime, datetime.datetime]:
|
) -> tuple[datetime.datetime, datetime.datetime]:
|
||||||
middle = abs(sunset - sunrise) / 2
|
total = abs(sunset - sunrise)
|
||||||
|
middle = total / 2
|
||||||
|
total = (
|
||||||
|
total.total_seconds() / 60 / 60 * (2 / 3)
|
||||||
|
) # about 12 hours normally.
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Calculate noon/midnight. Total diff: %s, middle: %s", total, middle
|
||||||
|
)
|
||||||
if sunset > sunrise:
|
if sunset > sunrise:
|
||||||
noon = sunrise + middle
|
noon = sunrise + middle
|
||||||
midnight = noon + timedelta(hours=12) * (1 if noon.hour < 12 else -1)
|
midnight = noon + timedelta(hours=total) * (
|
||||||
|
1 if noon.hour < total else -1
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
midnight = sunset + middle
|
midnight = sunset + middle
|
||||||
noon = midnight + timedelta(hours=12) * (
|
noon = midnight + timedelta(hours=total) * (
|
||||||
1 if midnight.hour < 12 else -1
|
1 if midnight.hour < total else -1
|
||||||
)
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Calculate noon/midnight. Noon: %s Midnight: %s", noon, midnight
|
||||||
|
)
|
||||||
return noon, midnight
|
return noon, midnight
|
||||||
|
|
||||||
location = self.astral_location
|
location = self.astral_location
|
||||||
|
|
@ -1612,8 +1767,18 @@ class SunLightSettings:
|
||||||
return self.sleep_brightness
|
return self.sleep_brightness
|
||||||
if percent > 0:
|
if percent > 0:
|
||||||
return self.max_brightness
|
return self.max_brightness
|
||||||
|
if self.adapt_until_sleep and self.adapt_brightness_until_sleep and percent < 0:
|
||||||
|
delta_brightness = abs(self.min_brightness - self.sleep_brightness)
|
||||||
|
return (delta_brightness * abs(1 + percent)) + self.sleep_brightness
|
||||||
delta_brightness = self.max_brightness - self.min_brightness
|
delta_brightness = self.max_brightness - self.min_brightness
|
||||||
percent = 1 + percent
|
percent = 1 + percent
|
||||||
|
if self.flat_limits:
|
||||||
|
if percent * 100 > self.max_brightness:
|
||||||
|
return self.max_brightness
|
||||||
|
elif percent * 100 < self.min_brightness:
|
||||||
|
return self.min_brightness
|
||||||
|
return percent * 100
|
||||||
|
delta_brightness = self.max_brightness - self.min_brightness
|
||||||
return (delta_brightness * percent) + self.min_brightness
|
return (delta_brightness * percent) + self.min_brightness
|
||||||
|
|
||||||
def calc_color_temp_kelvin(self, percent: float) -> int:
|
def calc_color_temp_kelvin(self, percent: float) -> int:
|
||||||
|
|
@ -1624,7 +1789,7 @@ class SunLightSettings:
|
||||||
return 5 * round(ct / 5) # round to nearest 5
|
return 5 * round(ct / 5) # round to nearest 5
|
||||||
if percent == 0 or not self.adapt_until_sleep:
|
if percent == 0 or not self.adapt_until_sleep:
|
||||||
return self.min_color_temp
|
return self.min_color_temp
|
||||||
if self.adapt_until_sleep and percent < 0:
|
if self.adapt_until_sleep and self.adapt_color_temp_until_sleep and percent < 0:
|
||||||
delta = abs(self.min_color_temp - self.sleep_color_temp)
|
delta = abs(self.min_color_temp - self.sleep_color_temp)
|
||||||
ct = (delta * abs(1 + percent)) + self.sleep_color_temp
|
ct = (delta * abs(1 + percent)) + self.sleep_color_temp
|
||||||
return 5 * round(ct / 5) # round to nearest 5
|
return 5 * round(ct / 5) # round to nearest 5
|
||||||
|
|
@ -1922,18 +2087,19 @@ class TurnOnOffListener:
|
||||||
def is_manually_controlled(
|
def is_manually_controlled(
|
||||||
self,
|
self,
|
||||||
switch: AdaptiveSwitch,
|
switch: AdaptiveSwitch,
|
||||||
light: str,
|
wlight: str,
|
||||||
|
mlight: str,
|
||||||
force: bool,
|
force: bool,
|
||||||
adapt_brightness: bool,
|
adapt_brightness: bool,
|
||||||
adapt_color: bool,
|
adapt_color: bool,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check if the light has been 'on' and is now manually controlled."""
|
"""Check if the light has been 'on' and is now manually controlled."""
|
||||||
manual_control = self.manual_control.setdefault(light, False)
|
manual_control = self.manual_control.setdefault(wlight, False)
|
||||||
if manual_control:
|
if manual_control:
|
||||||
# Manually controlled until light is turned on and off
|
# Manually controlled until light is turned on and off
|
||||||
return True
|
return True
|
||||||
|
|
||||||
turn_on_event = self.turn_on_event.get(light)
|
turn_on_event = self.turn_on_event.get(wlight)
|
||||||
if (
|
if (
|
||||||
turn_on_event is not None
|
turn_on_event is not None
|
||||||
and not is_our_context(turn_on_event.context)
|
and not is_our_context(turn_on_event.context)
|
||||||
|
|
@ -1946,13 +2112,13 @@ class TurnOnOffListener:
|
||||||
# Light was already on and 'light.turn_on' was not called by
|
# Light was already on and 'light.turn_on' was not called by
|
||||||
# the adaptive_lighting integration.
|
# the adaptive_lighting integration.
|
||||||
manual_control = True
|
manual_control = True
|
||||||
_fire_manual_control_event(switch, light, turn_on_event.context)
|
_fire_manual_control_event(switch, wlight, turn_on_event.context)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"'%s' was already on and 'light.turn_on' was not called by the"
|
"'%s' was already on and 'light.turn_on' was not called by the"
|
||||||
" adaptive_lighting integration (context.id='%s'), the Adaptive"
|
" adaptive_lighting integration (context.id='%s'), the Adaptive"
|
||||||
" Lighting will stop adapting the light until the switch or the"
|
" Lighting will stop adapting the light until the switch or the"
|
||||||
" light turns off and then on again.",
|
" light turns off and then on again.",
|
||||||
light,
|
mlight,
|
||||||
turn_on_event.context.id,
|
turn_on_event.context.id,
|
||||||
)
|
)
|
||||||
return manual_control
|
return manual_control
|
||||||
|
|
@ -1960,7 +2126,8 @@ class TurnOnOffListener:
|
||||||
async def significant_change(
|
async def significant_change(
|
||||||
self,
|
self,
|
||||||
switch: AdaptiveSwitch,
|
switch: AdaptiveSwitch,
|
||||||
light: str,
|
wlight: str,
|
||||||
|
mlight: str,
|
||||||
adapt_brightness: bool,
|
adapt_brightness: bool,
|
||||||
adapt_color: bool,
|
adapt_color: bool,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
@ -1972,35 +2139,77 @@ class TurnOnOffListener:
|
||||||
detected, we mark the light as 'manually controlled' until the light
|
detected, we mark the light as 'manually controlled' until the light
|
||||||
or switch is turned 'off' and 'on' again.
|
or switch is turned 'off' and 'on' again.
|
||||||
"""
|
"""
|
||||||
last_service_data = self.last_service_data.get(light)
|
last_service_data = self.last_service_data.get(wlight)
|
||||||
if last_service_data is None:
|
if last_service_data is None:
|
||||||
return
|
return
|
||||||
compare_to = functools.partial(
|
compare_to = functools.partial(
|
||||||
_attributes_have_changed,
|
_attributes_have_changed,
|
||||||
light=light,
|
light=wlight,
|
||||||
adapt_brightness=adapt_brightness,
|
adapt_brightness=adapt_brightness,
|
||||||
adapt_color=adapt_color,
|
adapt_color=adapt_color,
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
|
if switch._alt_detect_method or wlight != mlight:
|
||||||
|
old_states: list[State] = self.last_state_change[wlight]
|
||||||
|
_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,
|
||||||
|
wlight,
|
||||||
|
)
|
||||||
|
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",
|
||||||
|
wlight,
|
||||||
|
index,
|
||||||
|
context.id,
|
||||||
|
old_state,
|
||||||
|
prior_state,
|
||||||
|
)
|
||||||
|
_LOGGER.info(
|
||||||
|
"We will now set %s as manually controlled. (context.id=%s)",
|
||||||
|
wlight,
|
||||||
|
context.id,
|
||||||
|
)
|
||||||
|
return True
|
||||||
# Update state and check for a manual change not done in HA.
|
# Update state and check for a manual change not done in HA.
|
||||||
# Ensure HASS is correctly updating your light's state with
|
# Ensure HASS is correctly updating your light's state with
|
||||||
# light.turn_on calls if any problems arise. This
|
# light.turn_on calls if any problems arise. This
|
||||||
# can happen e.g. using zigbee2mqtt with 'report: false' in device settings.
|
# can happen e.g. using zigbee2mqtt with 'report: false' in device settings.
|
||||||
if switch._detect_non_ha_changes:
|
if switch._detect_non_ha_changes:
|
||||||
|
if wlight != mlight:
|
||||||
|
return # only supported with alt_detect_method
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s: 'detect_non_ha_changes: true', calling update_entity(%s)"
|
"%s: 'detect_non_ha_changes: true', calling update_entity(%s)"
|
||||||
" and check if it's last adapt succeeded.",
|
" and check if it's last adapt succeeded.",
|
||||||
switch._name,
|
switch._name,
|
||||||
light,
|
wlight,
|
||||||
)
|
)
|
||||||
# This update_entity probably isn't necessary now that we're checking
|
# This update_entity probably isn't necessary now that we're checking
|
||||||
# if transitions finished from our last adapt.
|
# if transitions finished from our last adapt.
|
||||||
await self.hass.helpers.entity_component.async_update_entity(light)
|
await self.hass.helpers.entity_component.async_update_entity(wlight)
|
||||||
refreshed_state = self.hass.states.get(light)
|
refreshed_state = self.hass.states.get(wlight)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s: Current state of %s: %s",
|
"%s: Current state of %s: %s",
|
||||||
switch._name,
|
switch._name,
|
||||||
light,
|
wlight,
|
||||||
refreshed_state,
|
refreshed_state,
|
||||||
)
|
)
|
||||||
changed = compare_to(
|
changed = compare_to(
|
||||||
|
|
@ -2010,7 +2219,7 @@ class TurnOnOffListener:
|
||||||
if changed:
|
if changed:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"State of '%s' didn't change wrt 'last_service_data' (context.id=%s)",
|
"State of '%s' didn't change wrt 'last_service_data' (context.id=%s)",
|
||||||
light,
|
wlight,
|
||||||
context.id,
|
context.id,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
@ -2018,7 +2227,7 @@ class TurnOnOffListener:
|
||||||
"%s: Light '%s' correctly matches our last adapt's service data, continuing..."
|
"%s: Light '%s' correctly matches our last adapt's service data, continuing..."
|
||||||
" context.id=%s.",
|
" context.id=%s.",
|
||||||
switch._name,
|
switch._name,
|
||||||
light,
|
wlight,
|
||||||
context.id,
|
context.id,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,15 @@
|
||||||
"description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have the adaptive_lighting entry defined in your YAML configuration.",
|
"description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have the adaptive_lighting entry defined in your YAML configuration.",
|
||||||
"data": {
|
"data": {
|
||||||
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
||||||
|
"watched_lights": "watched_lights: Use this dictionary of lights to check for manually controlled events in addition to the main lights 🌟 Example: {light.watch_light: light.main_light} will fire manually controlled events to light.main_light",
|
||||||
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
||||||
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
||||||
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
||||||
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||||
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
||||||
"transition_until_sleep": "transition_until_sleep: When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙",
|
"transition_until_sleep": "transition_until_sleep: This option ignores the current state of the sleep switch. When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning color temperature to these values after sunset. 🌙",
|
||||||
|
"adapt_brightness_until_sleep": "adapt_brightness_until_sleep: Only active when `transition_until_sleep` is true.",
|
||||||
|
"adapt_color_temp_until_sleep": "adapt_color_temp_until_sleep: Only active when `transition_until_sleep` is true.",
|
||||||
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
||||||
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
||||||
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
||||||
|
|
@ -43,7 +46,9 @@
|
||||||
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
||||||
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
|
"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`). 🔄",
|
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
|
||||||
|
"flat_limits": "flat_limits: When True, will not calculate between the max/min supported limits of your light. Example: when adapting brightness to 50% while max_brightness is set to 80%, Adaptive Lighting will use 80% instead of 90%",
|
||||||
"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`! 🔒",
|
"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`! 🔒",
|
||||||
|
"alt_detect_method": "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)",
|
||||||
"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: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
|
||||||
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
|
"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. ⏲️",
|
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from homeassistant.components.adaptive_lighting.const import (
|
||||||
ADAPT_BRIGHTNESS_SWITCH,
|
ADAPT_BRIGHTNESS_SWITCH,
|
||||||
ADAPT_COLOR_SWITCH,
|
ADAPT_COLOR_SWITCH,
|
||||||
ATTR_TURN_ON_OFF_LISTENER,
|
ATTR_TURN_ON_OFF_LISTENER,
|
||||||
|
CONF_ALT_DETECT_METHOD,
|
||||||
CONF_AUTORESET_CONTROL,
|
CONF_AUTORESET_CONTROL,
|
||||||
CONF_DETECT_NON_HA_CHANGES,
|
CONF_DETECT_NON_HA_CHANGES,
|
||||||
CONF_INITIAL_TRANSITION,
|
CONF_INITIAL_TRANSITION,
|
||||||
|
|
@ -23,9 +24,11 @@ from homeassistant.components.adaptive_lighting.const import (
|
||||||
CONF_SUNRISE_OFFSET,
|
CONF_SUNRISE_OFFSET,
|
||||||
CONF_SUNRISE_TIME,
|
CONF_SUNRISE_TIME,
|
||||||
CONF_SUNSET_TIME,
|
CONF_SUNSET_TIME,
|
||||||
|
CONF_TAKE_OVER_CONTROL,
|
||||||
CONF_TRANSITION,
|
CONF_TRANSITION,
|
||||||
CONF_TURN_ON_LIGHTS,
|
CONF_TURN_ON_LIGHTS,
|
||||||
CONF_USE_DEFAULTS,
|
CONF_USE_DEFAULTS,
|
||||||
|
CONF_WHICH_SWITCH,
|
||||||
CONST_COLOR,
|
CONST_COLOR,
|
||||||
DEFAULT_MAX_BRIGHTNESS,
|
DEFAULT_MAX_BRIGHTNESS,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
|
|
@ -61,7 +64,6 @@ from homeassistant.components.light import (
|
||||||
COLOR_MODE_BRIGHTNESS,
|
COLOR_MODE_BRIGHTNESS,
|
||||||
)
|
)
|
||||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||||
from homeassistant.components.light import SERVICE_TURN_OFF
|
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as config_util
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
|
@ -73,6 +75,8 @@ from homeassistant.const import (
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PLATFORM,
|
CONF_PLATFORM,
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
|
SERVICE_TOGGLE,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
|
|
@ -221,7 +225,9 @@ async def setup_lights_and_switch(hass, extra_conf=None):
|
||||||
CONF_SUNSET_TIME: datetime.time(SUNSET.hour),
|
CONF_SUNSET_TIME: datetime.time(SUNSET.hour),
|
||||||
CONF_INITIAL_TRANSITION: 0,
|
CONF_INITIAL_TRANSITION: 0,
|
||||||
CONF_TRANSITION: 0,
|
CONF_TRANSITION: 0,
|
||||||
CONF_DETECT_NON_HA_CHANGES: True,
|
CONF_ALT_DETECT_METHOD: False,
|
||||||
|
CONF_DETECT_NON_HA_CHANGES: False,
|
||||||
|
CONF_TAKE_OVER_CONTROL: True,
|
||||||
CONF_PREFER_RGB_COLOR: False,
|
CONF_PREFER_RGB_COLOR: False,
|
||||||
CONF_MIN_COLOR_TEMP: 2500, # to not coincide with sleep_color_temp
|
CONF_MIN_COLOR_TEMP: 2500, # to not coincide with sleep_color_temp
|
||||||
**(extra_conf or {}),
|
**(extra_conf or {}),
|
||||||
|
|
@ -748,7 +754,7 @@ async def test_manual_control(hass):
|
||||||
@pytest.mark.dependency(depends=[*GLOBAL_TEST_DEPENDENCIES, "test_manual_control"])
|
@pytest.mark.dependency(depends=[*GLOBAL_TEST_DEPENDENCIES, "test_manual_control"])
|
||||||
async def test_auto_reset_manual_control(hass):
|
async def test_auto_reset_manual_control(hass):
|
||||||
switch, (light, *_) = await setup_lights_and_switch(
|
switch, (light, *_) = await setup_lights_and_switch(
|
||||||
hass, {CONF_AUTORESET_CONTROL: 0.1}
|
hass, {CONF_AUTORESET_CONTROL: 0.2}
|
||||||
)
|
)
|
||||||
context = switch.create_context("test") # needs to be passed to update method
|
context = switch.create_context("test") # needs to be passed to update method
|
||||||
manual_control = switch.turn_on_off_listener.manual_control
|
manual_control = switch.turn_on_off_listener.manual_control
|
||||||
|
|
@ -993,7 +999,7 @@ async def test_state_change_handlers(hass):
|
||||||
|
|
||||||
# [Config options]:
|
# [Config options]:
|
||||||
transition_used = 2
|
transition_used = 2
|
||||||
total_events = 5
|
total_events = 6
|
||||||
|
|
||||||
async def set_brightness(val: int):
|
async def set_brightness(val: int):
|
||||||
# 'Unsafe' set but we know what we're doing.
|
# 'Unsafe' set but we know what we're doing.
|
||||||
|
|
@ -1114,58 +1120,95 @@ async def test_state_change_handlers(hass):
|
||||||
assert listener.transition_timers.get(ENTITY_LIGHT)
|
assert listener.transition_timers.get(ENTITY_LIGHT)
|
||||||
|
|
||||||
# 5. Execute some checks during a transition
|
# 5. Execute some checks during a transition
|
||||||
_LOGGER.debug("Test detect_non_ha_changes:")
|
|
||||||
switch._take_over_control = True
|
for i in range(2):
|
||||||
assert switch._take_over_control
|
if i == 0:
|
||||||
switch._detect_non_ha_changes = True
|
_LOGGER.debug("Test detect_non_ha_changes before a transition:")
|
||||||
assert switch._detect_non_ha_changes
|
switch._take_over_control = True
|
||||||
await asyncio.sleep(transition_used / 3)
|
assert switch._take_over_control
|
||||||
# Ensure the timer still exists
|
switch._detect_non_ha_changes = True
|
||||||
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
assert switch._detect_non_ha_changes
|
||||||
assert timer and timer.is_running()
|
switch._alt_detect_method = False
|
||||||
last_service_data = deepcopy(current_service_data)
|
assert not switch._alt_detect_method
|
||||||
await update()
|
elif i == 1:
|
||||||
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
_LOGGER.debug("Test alt_detect_method before a transition:")
|
||||||
await update()
|
switch._take_over_control = True
|
||||||
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
assert switch._take_over_control
|
||||||
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
switch._detect_non_ha_changes = False
|
||||||
assert timer and timer.is_running()
|
assert not switch._detect_non_ha_changes
|
||||||
# Ensure the light did not adapt during the transition.
|
switch._alt_detect_method = True
|
||||||
assert last_service_data == current_service_data
|
assert switch._alt_detect_method
|
||||||
|
await asyncio.sleep(transition_used / 3)
|
||||||
|
# Ensure the timer still exists
|
||||||
|
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
||||||
|
assert timer and timer.is_running()
|
||||||
|
last_service_data = deepcopy(current_service_data)
|
||||||
|
await update()
|
||||||
|
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
|
await update()
|
||||||
|
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
|
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
||||||
|
assert timer and timer.is_running()
|
||||||
|
# Ensure the light did not adapt during the transition.
|
||||||
|
assert last_service_data == current_service_data
|
||||||
|
|
||||||
# 6. Assert everything after the transition finishes.
|
# 6. Assert everything after the transition finishes.
|
||||||
await asyncio.sleep(transition_used)
|
await asyncio.sleep(transition_used)
|
||||||
assert listener.last_state_change.get(ENTITY_LIGHT)
|
|
||||||
assert len(listener.last_state_change[ENTITY_LIGHT]) == total_events
|
|
||||||
# Timer should be done and reset now.
|
|
||||||
# This is the assert that I can't fix.
|
|
||||||
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
|
||||||
assert not timer or not timer.is_running()
|
|
||||||
|
|
||||||
# build last service data
|
for i in range(2):
|
||||||
await update(force=False)
|
if i == 0:
|
||||||
|
_LOGGER.debug("Test detect_non_ha_changes after a transition:")
|
||||||
|
switch._take_over_control = True
|
||||||
|
assert switch._take_over_control
|
||||||
|
switch._detect_non_ha_changes = True
|
||||||
|
assert switch._detect_non_ha_changes
|
||||||
|
switch._alt_detect_method = False
|
||||||
|
assert not switch._alt_detect_method
|
||||||
|
elif i == 1:
|
||||||
|
_LOGGER.debug("Test alt_detect_method after a transition:")
|
||||||
|
switch._take_over_control = True
|
||||||
|
assert switch._take_over_control
|
||||||
|
switch._detect_non_ha_changes = False
|
||||||
|
assert not switch._detect_non_ha_changes
|
||||||
|
switch._alt_detect_method = True
|
||||||
|
assert switch._alt_detect_method
|
||||||
|
|
||||||
# force=True should not reset manual control.
|
assert listener.last_state_change.get(ENTITY_LIGHT)
|
||||||
await turn_light(True, brightness=40)
|
if i == 1:
|
||||||
await turn_light(True, brightness=20)
|
total_events = 2
|
||||||
await update(force=False)
|
assert len(listener.last_state_change[ENTITY_LIGHT]) == total_events
|
||||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
# Timer should be done and reset now.
|
||||||
await update(force=True)
|
timer = listener.transition_timers.get(ENTITY_LIGHT)
|
||||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
assert not timer or not timer.is_running()
|
||||||
|
|
||||||
# turn light off then on should reset manual control.
|
# build last service data
|
||||||
await turn_light(False)
|
await update(force=False)
|
||||||
await turn_light(True)
|
|
||||||
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
|
||||||
|
|
||||||
await turn_light(True, brightness=50)
|
# force=True should not reset manual control.
|
||||||
_LOGGER.debug("Test: Brightness set to %s", 50)
|
await turn_light(True, brightness=40)
|
||||||
|
await turn_light(True, brightness=20)
|
||||||
|
await update(force=False)
|
||||||
|
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
|
await update(force=True)
|
||||||
|
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
|
|
||||||
# On next update ENTITY_LIGHT should be marked as manually controlled
|
# turn light off then on should reset manual control.
|
||||||
await update(force=False)
|
await turn_light(False)
|
||||||
assert switch.turn_on_off_listener.last_service_data.get(ENTITY_LIGHT) is not None
|
await turn_light(True)
|
||||||
assert switch.turn_on_off_listener.last_state_change.get(ENTITY_LIGHT) is not None
|
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
|
||||||
|
await turn_light(True, brightness=50)
|
||||||
|
_LOGGER.debug("Test: Brightness set to %s", 50)
|
||||||
|
|
||||||
|
# On next update ENTITY_LIGHT should be marked as manually controlled
|
||||||
|
await update(force=False)
|
||||||
|
assert (
|
||||||
|
switch.turn_on_off_listener.last_service_data.get(ENTITY_LIGHT) is not None
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
switch.turn_on_off_listener.last_state_change.get(ENTITY_LIGHT) is not None
|
||||||
|
)
|
||||||
|
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(
|
@pytest.mark.dependency(
|
||||||
|
|
@ -1334,10 +1377,89 @@ async def test_area(hass):
|
||||||
assert light.entity_id not in switch.turn_on_off_listener.last_service_data
|
assert light.entity_id not in switch.turn_on_off_listener.last_service_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES)
|
||||||
|
async def test_switch_turn_on_off_toggle(hass):
|
||||||
|
"""Test adaptive_lighting.change_switch_settings service."""
|
||||||
|
switch, (_, _, light) = await setup_lights_and_switch(hass)
|
||||||
|
entity_id = switch.entity_id
|
||||||
|
assert entity_id not in switch._lights
|
||||||
|
|
||||||
|
async def turn_on(which: str, **kwargs):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
CONF_WHICH_SWITCH: which,
|
||||||
|
**kwargs,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async def turn_off(which: str, **kwargs):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
CONF_WHICH_SWITCH: which,
|
||||||
|
**kwargs,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async def toggle(which: str, **kwargs):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TOGGLE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
CONF_WHICH_SWITCH: which,
|
||||||
|
**kwargs,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test sleep
|
||||||
|
await turn_on("sleep")
|
||||||
|
assert switch.sleep_mode_switch.is_on
|
||||||
|
await turn_off("sleep")
|
||||||
|
assert not switch.sleep_mode_switch.is_on
|
||||||
|
await toggle("sleep")
|
||||||
|
assert switch.sleep_mode_switch.is_on
|
||||||
|
# Test brightness
|
||||||
|
await turn_on("brightness")
|
||||||
|
assert switch.adapt_brightness_switch.is_on
|
||||||
|
await turn_off("brightness")
|
||||||
|
assert not switch.adapt_brightness_switch.is_on
|
||||||
|
await toggle("brightness")
|
||||||
|
assert switch.adapt_brightness_switch.is_on
|
||||||
|
# Test color
|
||||||
|
await turn_on("color")
|
||||||
|
assert switch.adapt_color_switch.is_on
|
||||||
|
await turn_off("color")
|
||||||
|
assert not switch.adapt_color_switch.is_on
|
||||||
|
await toggle("color")
|
||||||
|
assert switch.adapt_color_switch.is_on
|
||||||
|
# Test main
|
||||||
|
await turn_on("main")
|
||||||
|
assert switch.is_on
|
||||||
|
await turn_off("main")
|
||||||
|
assert not switch.is_on
|
||||||
|
await toggle("main")
|
||||||
|
assert switch.is_on
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES)
|
@pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES)
|
||||||
async def test_change_switch_settings_service(hass):
|
async def test_change_switch_settings_service(hass):
|
||||||
"""Test adaptive_lighting.change_switch_settings service."""
|
"""Test adaptive_lighting.change_switch_settings service."""
|
||||||
switch, (_, _, light) = await setup_lights_and_switch(hass)
|
switch, (_, _, light) = await setup_lights_and_switch(hass)
|
||||||
|
switch2, (_, light2, _) = await setup_lights_and_switch(
|
||||||
|
hass, {CONF_NAME: "second_switch"}
|
||||||
|
)
|
||||||
entity_id = light.entity_id
|
entity_id = light.entity_id
|
||||||
assert entity_id not in switch._lights
|
assert entity_id not in switch._lights
|
||||||
|
|
||||||
|
|
@ -1346,7 +1468,6 @@ async def test_change_switch_settings_service(hass):
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: ENTITY_SWITCH,
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
|
|
@ -1355,12 +1476,16 @@ async def test_change_switch_settings_service(hass):
|
||||||
|
|
||||||
# Test changing sunrise offset
|
# Test changing sunrise offset
|
||||||
assert switch._sun_light_settings.sunrise_offset.total_seconds() == 0
|
assert switch._sun_light_settings.sunrise_offset.total_seconds() == 0
|
||||||
await change_switch_settings(**{CONF_SUNRISE_OFFSET: 10})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_SUNRISE_OFFSET: 10}
|
||||||
|
)
|
||||||
assert switch._sun_light_settings.sunrise_offset.total_seconds() == 10
|
assert switch._sun_light_settings.sunrise_offset.total_seconds() == 10
|
||||||
|
|
||||||
# Test changing max brightness
|
# Test changing max brightness
|
||||||
assert switch._sun_light_settings.max_brightness == 100
|
assert switch._sun_light_settings.max_brightness == 100
|
||||||
await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 50})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_MAX_BRIGHTNESS: 50}
|
||||||
|
)
|
||||||
assert switch._sun_light_settings.max_brightness == 50
|
assert switch._sun_light_settings.max_brightness == 50
|
||||||
|
|
||||||
# Test changing to illegal max brightness
|
# Test changing to illegal max brightness
|
||||||
|
|
@ -1368,20 +1493,33 @@ async def test_change_switch_settings_service(hass):
|
||||||
voluptuous.error.MultipleInvalid,
|
voluptuous.error.MultipleInvalid,
|
||||||
match="value must be at most 100 for dictionary",
|
match="value must be at most 100 for dictionary",
|
||||||
):
|
):
|
||||||
await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 5000})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_MAX_BRIGHTNESS: 5000}
|
||||||
|
)
|
||||||
|
|
||||||
# Change CONF_MIN_COLOR_TEMP, the factory default is 2000, but setup_lights_and_switch
|
# Change CONF_MIN_COLOR_TEMP, the factory default is 2000, but setup_lights_and_switch
|
||||||
# sets it to 2500
|
# sets it to 2500
|
||||||
assert switch._sun_light_settings.min_color_temp == 2500
|
assert switch._sun_light_settings.min_color_temp == 2500
|
||||||
|
|
||||||
# testing with "factory" should change it to 2000
|
# testing with "factory" should change it to 2000
|
||||||
await change_switch_settings(**{CONF_USE_DEFAULTS: "factory"})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "factory"}
|
||||||
|
)
|
||||||
assert switch._sun_light_settings.min_color_temp == 2000
|
assert switch._sun_light_settings.min_color_temp == 2000
|
||||||
|
|
||||||
# testing with "current" should not change things
|
# testing with "current" should not change things
|
||||||
await change_switch_settings(**{CONF_USE_DEFAULTS: "current"})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "current"}
|
||||||
|
)
|
||||||
assert switch._sun_light_settings.min_color_temp == 2000
|
assert switch._sun_light_settings.min_color_temp == 2000
|
||||||
|
|
||||||
# testing with "configuration" should revert back to 2500
|
# testing with "configuration" should revert back to 2500
|
||||||
await change_switch_settings(**{CONF_USE_DEFAULTS: "configuration"})
|
await change_switch_settings(
|
||||||
|
**{ATTR_ENTITY_ID: ENTITY_SWITCH, CONF_USE_DEFAULTS: "configuration"}
|
||||||
|
)
|
||||||
assert switch._sun_light_settings.min_color_temp == 2500
|
assert switch._sun_light_settings.min_color_temp == 2500
|
||||||
|
|
||||||
|
# testing with no switches or lights defined.
|
||||||
|
assert switch2._sun_light_settings.max_brightness == 100
|
||||||
|
await change_switch_settings(**{CONF_MAX_BRIGHTNESS: 50})
|
||||||
|
assert switch2._sun_light_settings.max_brightness == 50
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue