Merge branch 'master' into context-cleanup

This commit is contained in:
Bas Nijholt 2022-08-29 09:19:21 -07:00 • committed by GitHub
commit 34f485881d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 2274 additions and 187 deletions

View file

@ -1,13 +1,12 @@
"""Adaptive Lighting integration in Home-Assistant."""
import logging
from typing import Any, Dict
import voluptuous as vol
from typing import Any
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_SOURCE
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from .const import (
_DOMAIN_SCHEMA,
@ -36,7 +35,7 @@ CONFIG_SCHEMA = vol.Schema(
)
async def async_setup(hass: HomeAssistant, config: Dict[str, Any]):
async def async_setup(hass: HomeAssistant, config: dict[str, Any]):
"""Import integration from config."""
if DOMAIN in config:

View file

@ -1,12 +1,11 @@
"""Config flow for Adaptive Lighting integration."""
import logging
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from .const import ( # pylint: disable=unused-import
CONF_LIGHTS,

View file

@ -1,8 +1,7 @@
"""Constants for the Adaptive Lighting integration."""
import voluptuous as vol
from homeassistant.components.light import VALID_TRANSITION
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
ICON = "mdi:theme-light-dark"
@ -17,6 +16,7 @@ CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES = (
False,
)
CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1
CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION = "sleep_transition", 1
CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90
CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 100
CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP = "max_color_temp", 5500
@ -63,6 +63,7 @@ VALIDATION_TUPLES = [
(CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids),
(CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool),
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),

View file

@ -1,9 +1,12 @@
{
"domain": "adaptive_lighting",
"name": "Adaptive Lighting",
"documentation": "https://www.home-assistant.io/integrations/adaptive_lighting",
"documentation": "https://github.com/basnijholt/adaptive-lighting#readme",
"issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues",
"config_flow": true,
"dependencies": [],
"codeowners": ["@basnijholt"],
"requirements": []
"codeowners": ["@basnijholt", "@RubenKelevra"],
"version": "1.0.16",
"requirements": [],
"iot_class": "calculated"
}

View file

@ -5,7 +5,7 @@ apply:
description: entity_id of the Adaptive Lighting switch.
example: switch.adaptive_lighting_default
lights:
description: entity_id(s) of lights.
description: "entity_id(s) of lights, default: lights of the switch"
example: light.bedroom_ceiling
transition:
description: Transition of the lights.

View file

@ -1,5 +1,4 @@
{
"title": "Adaptive Lighting",
"config": {
"step": {
"user": {
@ -21,7 +20,8 @@
"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": {
"lights": "lights",
"initial_transition": "initial_transition, when lights go 'off' to 'on' or when 'sleep_state' changes",
"initial_transition": "initial_transition, when lights go 'off' to 'on'",
"sleep_transition": "sleep_transition, when 'sleep_state' changes",
"interval": "interval, time between switch updates in seconds",
"max_brightness": "max_brightness, in %",
"max_color_temp": "max_color_temp, in Kelvin",

View file

@ -12,11 +12,9 @@ from datetime import timedelta
import functools
import logging
import math
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any
import astral
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT,
@ -27,19 +25,27 @@ from homeassistant.components.light import (
ATTR_HS_COLOR,
ATTR_KELVIN,
ATTR_RGB_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
ATTR_XY_COLOR,
DOMAIN as LIGHT_DOMAIN,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_RGB,
COLOR_MODE_RGBW,
COLOR_MODE_XY,
)
from homeassistant.components.light import (
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
VALID_TRANSITION,
is_on,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DOMAIN,
@ -82,6 +88,7 @@ from homeassistant.util.color import (
color_xy_to_hs,
)
import homeassistant.util.dt as dt_util
import voluptuous as vol
from .const import (
ADAPT_BRIGHTNESS_SWITCH,
@ -103,6 +110,7 @@ from .const import (
CONF_SEPARATE_TURN_ON_COMMANDS,
CONF_SLEEP_BRIGHTNESS,
CONF_SLEEP_COLOR_TEMP,
CONF_SLEEP_TRANSITION,
CONF_SUNRISE_OFFSET,
CONF_SUNRISE_TIME,
CONF_SUNSET_OFFSET,
@ -125,7 +133,6 @@ from .const import (
_SUPPORT_OPTS = {
"brightness": SUPPORT_BRIGHTNESS,
"white_value": SUPPORT_WHITE_VALUE,
"color_temp": SUPPORT_COLOR_TEMP,
"color": SUPPORT_COLOR,
"transition": SUPPORT_TRANSITION,
@ -154,7 +161,6 @@ COLOR_ATTRS = { # Should ATTR_PROFILE be in here?
BRIGHTNESS_ATTRS = {
ATTR_BRIGHTNESS,
ATTR_WHITE_VALUE,
ATTR_BRIGHTNESS_PCT,
ATTR_BRIGHTNESS_STEP,
ATTR_BRIGHTNESS_STEP_PCT,
@ -176,7 +182,9 @@ def _short_hash(string: str, length: int = 4) -> str:
return base64.b85encode(str_hash_bytes)[:length]
def create_context(name: str, which: str, index: int) -> Context:
def create_context(
name: str, which: str, index: int, parent: Context | None = None
) -> Context:
"""Create a context that can identify this integration."""
# Use a hash for the name because otherwise the context might become
# too long (max len == 36) to fit in the database.
@ -185,23 +193,52 @@ def create_context(name: str, which: str, index: int) -> Context:
# before we exceed the 36-character limit and are forced to wrap.
index_packed = base64.b85encode(_int_to_bytes(index, signed=False))
context_id = f"{_DOMAIN_SHORT}:{name_hash}:{which}:{index_packed}"[:36]
return Context(id=context_id)
parent_id = parent.id if parent else None
return Context(id=context_id, parent_id=parent_id)
def is_our_context(context: Optional[Context]) -> bool:
def is_our_context(context: Context | None) -> bool:
"""Check whether this integration created 'context'."""
if context is None:
return False
return context.id.startswith(_DOMAIN_SHORT)
def _split_service_data(service_data, adapt_brightness, adapt_color):
"""Split service_data into two dictionaries (for color and brightness)."""
transition = service_data.get(ATTR_TRANSITION)
if transition is not None:
# Split the transition over both commands
service_data[ATTR_TRANSITION] /= 2
service_datas = []
if adapt_color:
service_data_color = service_data.copy()
service_data_color.pop(ATTR_BRIGHTNESS, None)
service_datas.append(service_data_color)
if adapt_brightness:
service_data_brightness = service_data.copy()
service_data_brightness.pop(ATTR_RGB_COLOR, None)
service_data_brightness.pop(ATTR_COLOR_TEMP, None)
service_datas.append(service_data_brightness)
if not service_datas: # neither adapt_brightness nor adapt_color
return [service_data]
return service_datas
async def handle_apply(switch: AdaptiveSwitch, service_call: ServiceCall):
"""Handle the entity service apply."""
hass = switch.hass
data = service_call.data
all_lights = _expand_light_groups(hass, data[CONF_LIGHTS])
all_lights = data[CONF_LIGHTS]
if not all_lights:
all_lights = switch._lights
all_lights = _expand_light_groups(hass, all_lights)
switch.turn_on_off_listener.lights.update(all_lights)
_LOGGER.debug(
"Called 'adaptive_lighting.apply' service with '%s'",
data,
)
for light in all_lights:
if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light):
await switch._adapt_light( # pylint: disable=protected-access
@ -211,6 +248,7 @@ async def handle_apply(switch: AdaptiveSwitch, service_call: ServiceCall):
data[ATTR_ADAPT_COLOR],
data[CONF_PREFER_RGB_COLOR],
force=True,
context=switch.create_context("service", parent=service_call.context),
)
@ -228,25 +266,36 @@ async def handle_set_manual_control(switch: AdaptiveSwitch, service_call: Servic
if service_call.data[CONF_MANUAL_CONTROL]:
for light in all_lights:
switch.turn_on_off_listener.manual_control[light] = True
_fire_manual_control_event(switch.hass, light, service_call.context)
_fire_manual_control_event(switch, light, service_call.context)
else:
switch.turn_on_off_listener.reset(*all_lights)
# pylint: disable=protected-access
await switch._adapt_lights(
all_lights,
transition=switch._initial_transition,
force=True,
context=switch.create_context("service"),
)
if switch.is_on:
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
def _fire_manual_control_event(
hass: HomeAssistant, light: str, context: Context, is_async=True
switch: AdaptiveSwitch, light: str, context: Context, is_async=True
):
"""Fire an event that 'light' is marked as manual_control."""
hass = switch.hass
fire = hass.bus.async_fire if is_async else hass.bus.fire
fire(f"{DOMAIN}.manual_control", {ATTR_ENTITY_ID: light}, context=context)
_LOGGER.debug(
"'adaptive_lighting.manual_control' event fired for %s for light %s",
switch.entity_id,
light,
)
fire(
f"{DOMAIN}.manual_control",
{ATTR_ENTITY_ID: light, SWITCH_DOMAIN: switch.entity_id},
context=context,
)
async def async_setup_entry(
@ -287,7 +336,9 @@ async def async_setup_entry(
platform.async_register_entity_service(
SERVICE_APPLY,
{
vol.Required(CONF_LIGHTS): cv.entity_ids,
vol.Optional(
CONF_LIGHTS, default=[]
): cv.entity_ids, # pylint: disable=protected-access
vol.Optional(
CONF_TRANSITION,
default=switch._initial_transition, # pylint: disable=protected-access
@ -324,7 +375,7 @@ def validate(config_entry: ConfigEntry):
return data
def match_switch_state_event(event: Event, from_or_to_state: List[str]):
def match_switch_state_event(event: Event, from_or_to_state: list[str]):
"""Match state event when either 'from_state' or 'to_state' matches."""
old_state = event.data.get("old_state")
from_state_match = old_state is not None and old_state.state in from_or_to_state
@ -336,7 +387,7 @@ def match_switch_state_event(event: Event, from_or_to_state: List[str]):
return match
def _expand_light_groups(hass: HomeAssistant, lights: List[str]) -> List[str]:
def _expand_light_groups(hass: HomeAssistant, lights: list[str]) -> list[str]:
all_lights = set()
turn_on_off_listener = hass.data[DOMAIN][ATTR_TURN_ON_OFF_LISTENER]
for light in lights:
@ -357,11 +408,34 @@ def _expand_light_groups(hass: HomeAssistant, lights: List[str]) -> List[str]:
def _supported_features(hass: HomeAssistant, light: str):
state = hass.states.get(light)
supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
return {key for key, value in _SUPPORT_OPTS.items() if supported_features & value}
supported = {
key for key, value in _SUPPORT_OPTS.items() if supported_features & value
}
supported_color_modes = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES, set())
if COLOR_MODE_RGB in supported_color_modes:
supported.add("color")
# Adding brightness here, see
# comment https://github.com/basnijholt/adaptive-lighting/issues/112#issuecomment-836944011
supported.add("brightness")
if COLOR_MODE_RGBW in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_XY in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_HS in supported_color_modes:
supported.add("color")
supported.add("brightness") # see above url
if COLOR_MODE_COLOR_TEMP in supported_color_modes:
supported.add("color_temp")
supported.add("brightness") # see above url
if COLOR_MODE_BRIGHTNESS in supported_color_modes:
supported.add("brightness")
return supported
def color_difference_redmean(
rgb1: Tuple[float, float, float], rgb2: Tuple[float, float, float]
rgb1: tuple[float, float, float], rgb2: tuple[float, float, float]
) -> float:
"""Distance between colors in RGB space (redmean metric).
@ -372,17 +446,17 @@ def color_difference_redmean(
- https://www.compuphase.com/cmetric.htm
"""
r_hat = (rgb1[0] + rgb2[0]) / 2
delta_r, delta_g, delta_b = [(col1 - col2) for col1, col2 in zip(rgb1, rgb2)]
red_term = (2 + r_hat / 256) * delta_r ** 2
green_term = 4 * delta_g ** 2
blue_term = (2 + (255 - r_hat) / 256) * delta_b ** 2
delta_r, delta_g, delta_b = ((col1 - col2) for col1, col2 in zip(rgb1, rgb2))
red_term = (2 + r_hat / 256) * delta_r**2
green_term = 4 * delta_g**2
blue_term = (2 + (255 - r_hat) / 256) * delta_b**2
return math.sqrt(red_term + green_term + blue_term)
def _attributes_have_changed(
light: str,
old_attributes: Dict[str, Any],
new_attributes: Dict[str, Any],
old_attributes: dict[str, Any],
new_attributes: dict[str, Any],
adapt_brightness: bool,
adapt_color: bool,
context: Context,
@ -405,24 +479,6 @@ def _attributes_have_changed(
)
return True
if (
adapt_brightness
and ATTR_WHITE_VALUE in old_attributes
and ATTR_WHITE_VALUE in new_attributes
):
last_white_value = old_attributes[ATTR_WHITE_VALUE]
current_white_value = new_attributes[ATTR_WHITE_VALUE]
if abs(current_white_value - last_white_value) > BRIGHTNESS_CHANGE:
_LOGGER.debug(
"White Value of '%s' significantly changed from %s to %s with"
" context.id='%s'",
light,
last_white_value,
current_white_value,
context.id,
)
return True
if (
adapt_color
and ATTR_COLOR_TEMP in old_attributes
@ -501,18 +557,24 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES]
self._initial_transition = data[CONF_INITIAL_TRANSITION]
self._sleep_transition = data[CONF_SLEEP_TRANSITION]
self._interval = data[CONF_INTERVAL]
self._only_once = data[CONF_ONLY_ONCE]
self._prefer_rgb_color = data[CONF_PREFER_RGB_COLOR]
self._separate_turn_on_commands = data[CONF_SEPARATE_TURN_ON_COMMANDS]
self._take_over_control = data[CONF_TAKE_OVER_CONTROL]
self._transition = min(
data[CONF_TRANSITION], self._interval.total_seconds() // 2
)
self._transition = data[CONF_TRANSITION]
_loc = get_astral_location(self.hass)
if isinstance(_loc, tuple):
# Astral v2.2
location, _ = _loc
else:
# Astral v1
location = _loc
self._sun_light_settings = SunLightSettings(
name=self._name,
astral_location=get_astral_location(self.hass),
astral_location=location,
max_brightness=data[CONF_MAX_BRIGHTNESS],
max_color_temp=data[CONF_MAX_COLOR_TEMP],
min_brightness=data[CONF_MIN_BRIGHTNESS],
@ -524,6 +586,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
sunset_offset=data[CONF_SUNSET_OFFSET],
sunset_time=data[CONF_SUNSET_TIME],
time_zone=self.hass.config.time_zone,
transition=data[CONF_TRANSITION],
)
# Set other attributes
@ -531,16 +594,16 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._state = None
# Tracks 'off' → 'on' state changes
self._on_to_off_event: Dict[str, Event] = {}
self._on_to_off_event: dict[str, Event] = {}
# Tracks 'on' → 'off' state changes
self._off_to_on_event: Dict[str, Event] = {}
self._off_to_on_event: dict[str, Event] = {}
# Locks that prevent light adjusting when waiting for a light to 'turn_off'
self._locks: Dict[str, asyncio.Lock] = {}
self._locks: dict[str, asyncio.Lock] = {}
# To count the number of `Context` instances
self._context_cnt: int = 0
# Set in self._update_attrs_and_maybe_adapt_lights
self._settings: Dict[str, Any] = {}
self._settings: dict[str, Any] = {}
# Set and unset tracker in async_turn_on and async_turn_off
self.remove_listeners = []
@ -566,7 +629,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
return self._name
@property
def is_on(self) -> Optional[bool]:
def is_on(self) -> bool | None:
"""Return true if adaptive lighting is on."""
return self._state
@ -632,7 +695,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
return self._icon
@property
def device_state_attributes(self) -> Dict[str, Any]:
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the attributes of the switch."""
if not self.is_on:
return {key: None for key in self._settings}
@ -643,7 +706,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
]
return dict(self._settings, manual_control=manual_control)
def create_context(self, which: str = "default") -> Context:
def create_context(
self, which: str = "default", parent: Context | None = None
) -> Context:
"""Create a context that identifies this Adaptive Lighting instance."""
# Right now the highest number of each context_id it can create is
# 'adapt_lgt:XXXX:turn_on:*************'
@ -655,7 +720,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
# The smallest space we have is for adapt_lights, which has
# 8 characters. In base85 encoding, that's enough space to hold values
# up to 2**48 - 1, which should give us plenty of calls before we wrap.
context = create_context(self._name, which, self._context_cnt)
context = create_context(self._name, which, self._context_cnt, parent=parent)
self._context_cnt += 1
return context
@ -688,18 +753,20 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
async def _async_update_at_interval(self, now=None) -> None:
await self._update_attrs_and_maybe_adapt_lights(
force=False, context=self.create_context("interval")
transition=self._transition,
force=False,
context=self.create_context("interval"),
)
async def _adapt_light(
self,
light: str,
transition: Optional[int] = None,
adapt_brightness: Optional[bool] = None,
adapt_color: Optional[bool] = None,
prefer_rgb_color: Optional[bool] = None,
transition: int | None = None,
adapt_brightness: bool | None = None,
adapt_color: bool | None = None,
prefer_rgb_color: bool | None = None,
force: bool = False,
context: Optional[Context] = None,
context: Context | None = None,
) -> None:
lock = self._locks.get(light)
if lock is not None and lock.locked():
@ -724,10 +791,6 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
brightness = round(255 * self._settings["brightness_pct"] / 100)
service_data[ATTR_BRIGHTNESS] = brightness
if "white_value" in features and adapt_brightness:
white_value = round(255 * self._settings["brightness_pct"] / 100)
service_data[ATTR_WHITE_VALUE] = white_value
if (
"color_temp" in features
and adapt_color
@ -747,6 +810,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
and self._detect_non_ha_changes
and not force
and await self.turn_on_off_listener.significant_change(
self,
light,
adapt_brightness,
adapt_color,
@ -754,23 +818,16 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
)
):
return
_LOGGER.debug(
"%s: Scheduling 'light.turn_on' with the following 'service_data': %s"
" with context.id='%s'",
self._name,
service_data,
context.id,
)
self.turn_on_off_listener.last_service_data[light] = service_data
if self._separate_turn_on_commands:
service_datas = [
{ATTR_ENTITY_ID: light, key: value}
for key, value in service_data.items()
if key != ATTR_ENTITY_ID
]
else:
service_datas = [service_data]
for service_data in service_datas:
async def turn_on(service_data):
_LOGGER.debug(
"%s: Scheduling 'light.turn_on' with the following 'service_data': %s"
" with context.id='%s'",
self._name,
service_data,
context.id,
)
await self.hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@ -778,12 +835,26 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
context=context,
)
if not self._separate_turn_on_commands:
await turn_on(service_data)
else:
# Could be a list of length 1 or 2
service_datas = _split_service_data(
service_data, adapt_brightness, adapt_color
)
await turn_on(service_datas[0])
if len(service_datas) == 2:
transition = service_datas[0].get(ATTR_TRANSITION)
if transition is not None:
await asyncio.sleep(transition)
await turn_on(service_datas[1])
async def _update_attrs_and_maybe_adapt_lights(
self,
lights: Optional[List[str]] = None,
transition: Optional[int] = None,
lights: list[str] | None = None,
transition: int | None = None,
force: bool = False,
context: Optional[Context] = None,
context: Context | None = None,
) -> None:
assert context is not None
_LOGGER.debug(
@ -793,7 +864,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
)
assert self.is_on
self._settings = self._sun_light_settings.get_settings(
self.sleep_mode_switch.is_on
self.sleep_mode_switch.is_on, transition
)
self.async_write_ha_state()
if lights is None:
@ -804,10 +875,10 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
async def _adapt_lights(
self,
lights: List[str],
transition: Optional[int],
lights: list[str],
transition: int | None,
force: bool,
context: Optional[Context],
context: Context | None,
) -> None:
assert context is not None
_LOGGER.debug(
@ -824,6 +895,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
if (
self._take_over_control
and self.turn_on_off_listener.is_manually_controlled(
self,
light,
force,
self.adapt_brightness_switch.is_on,
@ -841,6 +913,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
async def _sleep_mode_switch_state_event(self, event: Event) -> None:
if not match_switch_state_event(event, (STATE_ON, STATE_OFF)):
_LOGGER.debug("%s: Ignoring sleep event %s", self._name, event)
return
_LOGGER.debug(
"%s: _sleep_mode_switch_state_event, event: '%s'", self._name, event
@ -848,9 +921,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
# Reset the manually controlled status when the "sleep mode" changes
self.turn_on_off_listener.reset(*self._lights)
await self._update_attrs_and_maybe_adapt_lights(
transition=self._initial_transition,
transition=self._sleep_transition,
force=True,
context=self.create_context("sleep"),
context=self.create_context("sleep", parent=event.context),
)
async def _light_event(self, event: Event) -> None:
@ -891,7 +964,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
lights=[entity_id],
transition=self._initial_transition,
force=True,
context=self.create_context("light_event"),
context=self.create_context("light_event", parent=event.context),
)
elif (
old_state is not None
@ -937,7 +1010,7 @@ class SimpleSwitch(SwitchEntity, RestoreEntity):
return self._icon
@property
def is_on(self) -> Optional[bool]:
def is_on(self) -> bool | None:
"""Return true if adaptive lighting is on."""
return self._state
@ -954,10 +1027,12 @@ class SimpleSwitch(SwitchEntity, RestoreEntity):
async def async_turn_on(self, **kwargs) -> None:
"""Turn on adaptive lighting sleep mode."""
_LOGGER.debug("%s: Turning on", self._name)
self._state = True
async def async_turn_off(self, **kwargs) -> None:
"""Turn off adaptive lighting sleep mode."""
_LOGGER.debug("%s: Turning off", self._name)
self._state = False
@ -973,21 +1048,41 @@ class SunLightSettings:
min_color_temp: int
sleep_brightness: int
sleep_color_temp: int
sunrise_offset: Optional[datetime.timedelta]
sunrise_time: Optional[datetime.time]
sunset_offset: Optional[datetime.timedelta]
sunset_time: Optional[datetime.time]
sunrise_offset: datetime.timedelta | None
sunrise_time: datetime.time | None
sunset_offset: datetime.timedelta | None
sunset_time: datetime.time | None
time_zone: datetime.tzinfo
transition: int
def get_sun_events(self, date: datetime.datetime) -> Dict[str, float]:
def get_sun_events(self, date: datetime.datetime) -> dict[str, float]:
"""Get the four sun event's timestamps at 'date'."""
def _replace_time(date: datetime.datetime, key: str) -> datetime.datetime:
time = getattr(self, f"{key}_time")
date_time = datetime.datetime.combine(date, time)
utc_time = self.time_zone.localize(date_time).astimezone(dt_util.UTC)
try: # HA ≤2021.05, https://github.com/basnijholt/adaptive-lighting/issues/128
utc_time = self.time_zone.localize(date_time).astimezone(dt_util.UTC)
except AttributeError: # HA ≥2021.06
utc_time = date_time.replace(
tzinfo=dt_util.DEFAULT_TIME_ZONE
).astimezone(dt_util.UTC)
return utc_time
def calculate_noon_and_midnight(
sunset: datetime.datetime, sunrise: datetime.datetime
) -> tuple[datetime.datetime, datetime.datetime]:
middle = abs(sunset - sunrise) / 2
if sunset > sunrise:
noon = sunrise + middle
midnight = noon + timedelta(hours=12) * (1 if noon.hour < 12 else -1)
else:
midnight = sunset + middle
noon = midnight + timedelta(hours=12) * (
1 if midnight.hour < 12 else -1
)
return noon, midnight
location = self.astral_location
sunrise = (
@ -1002,11 +1097,16 @@ class SunLightSettings:
) + self.sunset_offset
if self.sunrise_time is None and self.sunset_time is None:
solar_noon = location.solar_noon(date, local=False)
solar_midnight = location.solar_midnight(date, local=False)
try:
# Astral v1
solar_noon = location.solar_noon(date, local=False)
solar_midnight = location.solar_midnight(date, local=False)
except AttributeError:
# Astral v2
solar_noon = location.noon(date, local=False)
solar_midnight = location.midnight(date, local=False)
else:
solar_noon = sunrise + (sunset - sunrise) / 2
solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2
(solar_noon, solar_midnight) = calculate_noon_and_midnight(sunset, sunrise)
events = [
(SUN_EVENT_SUNRISE, sunrise.timestamp()),
@ -1029,7 +1129,7 @@ class SunLightSettings:
return events
def relevant_events(self, now: datetime.datetime) -> List[Tuple[str, float]]:
def relevant_events(self, now: datetime.datetime) -> list[tuple[str, float]]:
"""Get the previous and next sun event."""
events = [
self.get_sun_events(now + timedelta(days=days)) for days in [-1, 0, 1]
@ -1039,11 +1139,13 @@ class SunLightSettings:
i_now = bisect.bisect([ts for _, ts in events], now.timestamp())
return events[i_now - 1 : i_now + 1]
def calc_percent(self) -> float:
def calc_percent(self, transition: int) -> float:
"""Calculate the position of the sun in %."""
now = dt_util.utcnow()
now_ts = now.timestamp()
today = self.relevant_events(now)
target_time = now + timedelta(seconds=transition)
target_ts = target_time.timestamp()
today = self.relevant_events(target_time)
(_, prev_ts), (next_event, next_ts) = today
h, x = ( # pylint: disable=invalid-name
(prev_ts, next_ts)
@ -1051,7 +1153,7 @@ class SunLightSettings:
else (next_ts, prev_ts)
)
k = 1 if next_event in (SUN_EVENT_SUNSET, SUN_EVENT_NOON) else -1
percentage = (0 - k) * ((now_ts - h) / (h - x)) ** 2 + k
percentage = (0 - k) * ((target_ts - h) / (h - x)) ** 2 + k
return percentage
def calc_brightness_pct(self, percent: float, is_sleep: bool) -> float:
@ -1074,21 +1176,25 @@ class SunLightSettings:
return self.min_color_temp
def get_settings(
self, is_sleep
) -> Dict[str, Union[float, Tuple[float, float], Tuple[float, float, float]]]:
self, is_sleep, transition
) -> dict[str, float | tuple[float, float] | tuple[float, float, float]]:
"""Get all light settings.
Calculating all values takes <0.5ms.
"""
percent = self.calc_percent()
percent = (
self.calc_percent(transition)
if transition is not None
else self.calc_percent(0)
)
brightness_pct = self.calc_brightness_pct(percent, is_sleep)
color_temp_kelvin = self.calc_color_temp_kelvin(percent, is_sleep)
color_temp_mired: float = color_temperature_kelvin_to_mired(color_temp_kelvin)
rgb_color: Tuple[float, float, float] = color_temperature_to_rgb(
rgb_color: tuple[float, float, float] = color_temperature_to_rgb(
color_temp_kelvin
)
xy_color: Tuple[float, float] = color_RGB_to_xy(*rgb_color)
hs_color: Tuple[float, float] = color_xy_to_hs(*xy_color)
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
hs_color: tuple[float, float] = color_xy_to_hs(*xy_color)
return {
"brightness_pct": brightness_pct,
"color_temp_kelvin": color_temp_kelvin,
@ -1109,19 +1215,19 @@ class TurnOnOffListener:
self.lights = set()
# Tracks 'light.turn_off' service calls
self.turn_off_event: Dict[str, Event] = {}
self.turn_off_event: dict[str, Event] = {}
# Tracks 'light.turn_on' service calls
self.turn_on_event: Dict[str, Event] = {}
self.turn_on_event: dict[str, Event] = {}
# Keep 'asyncio.sleep' tasks that can be cancelled by 'light.turn_on' events
self.sleep_tasks: Dict[str, asyncio.Task] = {}
self.sleep_tasks: dict[str, asyncio.Task] = {}
# Tracks which lights are manually controlled
self.manual_control: Dict[str, bool] = {}
self.manual_control: dict[str, bool] = {}
# Counts the number of times (in a row) a light had a changed state.
self.cnt_significant_changes: Dict[str, int] = defaultdict(int)
self.cnt_significant_changes: dict[str, int] = defaultdict(int)
# Track 'state_changed' events of self.lights resulting from this integration
self.last_state_change: Dict[str, List[State]] = {}
self.last_state_change: dict[str, list[State]] = {}
# Track last 'service_data' to 'light.turn_on' resulting from this integration
self.last_service_data: Dict[str, Dict[str, Any]] = {}
self.last_service_data: dict[str, dict[str, Any]] = {}
# When a state is different `max_cnt_significant_changes` times in a row,
# mark it as manually_controlled.
@ -1151,7 +1257,7 @@ class TurnOnOffListener:
service = event.data[ATTR_SERVICE]
service_data = event.data[ATTR_SERVICE_DATA]
entity_ids = cv.ensure_list(service_data[ATTR_ENTITY_ID])
entity_ids = cv.ensure_list_csv(service_data[ATTR_ENTITY_ID])
if not any(eid in self.lights for eid in entity_ids):
return
@ -1211,7 +1317,7 @@ class TurnOnOffListener:
# called with a color_temp outside of its range (and HA reports the
# incorrect 'min_mireds' and 'max_mireds', which happens e.g., for
# Philips Hue White GU10 Bluetooth lights).
old_state: Optional[List[State]] = self.last_state_change.get(entity_id)
old_state: list[State] | None = self.last_state_change.get(entity_id)
if (
old_state is not None
and old_state[0].context.id == new_state.context.id
@ -1230,6 +1336,7 @@ class TurnOnOffListener:
def is_manually_controlled(
self,
switch: AdaptiveSwitch,
light: str,
force: bool,
adapt_brightness: bool,
@ -1254,7 +1361,7 @@ class TurnOnOffListener:
# Light was already on and 'light.turn_on' was not called by
# the adaptive_lighting integration.
manual_control = self.manual_control[light] = True
_fire_manual_control_event(self.hass, light, turn_on_event.context)
_fire_manual_control_event(switch, light, turn_on_event.context)
_LOGGER.debug(
"'%s' was already on and 'light.turn_on' was not called by the"
" adaptive_lighting integration (context.id='%s'), the Adaptive"
@ -1267,6 +1374,7 @@ class TurnOnOffListener:
async def significant_change(
self,
switch: AdaptiveSwitch,
light: str,
adapt_brightness: bool,
adapt_color: bool,
@ -1281,7 +1389,7 @@ class TurnOnOffListener:
"""
if light not in self.last_state_change:
return False
old_states: List[State] = self.last_state_change[light]
old_states: list[State] = self.last_state_change[light]
await self.hass.helpers.entity_component.async_update_entity(light)
new_state = self.hass.states.get(light)
compare_to = functools.partial(
@ -1325,7 +1433,7 @@ class TurnOnOffListener:
# N times in a row. We do this because sometimes a state changes
# happens only *after* a new update interval has already started.
self.manual_control[light] = True
_fire_manual_control_event(self.hass, light, context, is_async=False)
_fire_manual_control_event(switch, light, context, is_async=False)
else:
if n_changes > 1:
_LOGGER.debug(
@ -1339,7 +1447,7 @@ class TurnOnOffListener:
return changed
async def maybe_cancel_adjusting(
self, entity_id: str, off_to_on_event: Event, on_to_off_event: Optional[Event]
self, entity_id: str, off_to_on_event: Event, on_to_off_event: Event | None
) -> bool:
"""Cancel the adjusting of a light if it has just been turned off.

View file

@ -0,0 +1,49 @@
{
"title": "Adaptiv Belysning",
"config": {
"step": {
"user": {
"title": "Vælg et navn for denne Adaptive Belysning",
"description": "Vælg et navn til denne konfiguration. Du kan køre flere konfigurationer af Adaptiv Belysning, og hver af dem kan indeholde flere lys!",
"data": {
"name": "Navn"
}
}
},
"abort": {
"already_configured": "Denne enhed er allerede konfigureret"
}
},
"options": {
"step": {
"init": {
"title": "Adaptiv Belysnings indstillinger",
"description": "Alle indstillinger tilhørende en Adaptiv Belysnings komponent. Indstillingernes navne svarer til YAML indstillingernes. Ingen indstillinger vises hvis du allerede har konfigureret den i YAML.",
"data": {
"lights": "lights: lyskilder",
"initial_transition": "initial_transition: Hvor lang overgang når lyset går fra 'off' til 'on' eller når 'sleep_state' skiftes. (i sekunder)",
"interval": "interval: Tid imellem opdateringer (i sekunder)",
"max_brightness": "max_brightness: Højeste lysstyrke i cyklussen. (%)",
"max_color_temp": "max_color_temp: Koldeste lystemperatur i cyklussen. (Kelvin)",
"min_brightness": "min_brightness: Laveste lysstyrke i cyklussen. (%)",
"min_color_temp": "min_color_temp: Varmeste lystemperatur i cyklussen. (Kelvin)",
"only_once": "only_once: Juster udelukkende lysene adaptivt i øjeblikket de tændes.",
"prefer_rgb_color": "prefer_rgb_color: Brug 'rgb_color' istedet for 'color_temp' når muligt.",
"separate_turn_on_commands": "separate_turn_on_commands: Adskil kommandoerne for hver attribut (color, brightness, etc.) ved 'light.turn_on' (nødvendigt for bestemte lys).",
"sleep_brightness": "sleep_brightness, Lysstyrke for Sleep Mode. (%)",
"sleep_color_temp": "sleep_color_temp: Farvetemperatur under Sleep Mode. (Kelvin)",
"sunrise_offset": "sunrise_offset: Hvor længe før (-) eller efter (+) at definere solopgangen i cyklussen (+/- sekunder)",
"sunrise_time": "sunrise_time: Manuel overstyring af solopgangstidspunktet, hvis 'None', bruges det egentlige tidspunkt din lokation. (HH:MM:SS)",
"sunset_offset": "sunset_offset: Hvor længe før (-) eller efter (+) at definere solnedgangen i cyklussen (+/- sekunder)",
"sunset_time": "sunset_time: Manuel overstyring af solnedgangstidspunktet, hvis 'None', bruges det egentlige tidspunkt for din lokation. (HH:MM:SS)",
"take_over_control": "take_over_control: Hvis andet end Adaptiv Belysning kalder 'light.turn_on' på et lys der allerede er tændt, afbryd adaptering af lyset indtil at det tændes igen.",
"detect_non_ha_changes": "detect_non_ha_changes: Registrer alle ændringer på >10% på et lys (også udenfor HA), kræver at 'take_over_control' er slået til (kalder 'homeassistant.update_entity' hvert 'interval'!)",
"transition": "Overgangsperiode når en ændring i lyset udføres (i sekunder)"
}
}
},
"error": {
"option_error": "Ugyldig indstilling"
}
}
}

View file

@ -46,4 +46,4 @@
"option_error": "Fehlerhafte Option"
}
}
}
}

View file

@ -3,42 +3,43 @@
"config": {
"step": {
"user": {
"title": "Choose a name for the Adaptive Lighting",
"description": "Every instance can contain multiple lights!",
"title": "Choose a name for the Adaptive Lighting instance",
"description": "Pick a name for this instance. You can run several instances of Adaptive lighting, each of these can contain multiple lights!",
"data": {
"name": "Name"
}
}
},
"abort": {
"already_configured": "Device is already configured"
"already_configured": "This device is already configured"
}
},
"options": {
"step": {
"init": {
"title": "Adaptive Lighting options",
"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 the adaptive_lighting entry defined in your YAML configuration.",
"data": {
"lights": "lights",
"initial_transition": "initial_transition, when lights go 'off' to 'on' or when 'sleep_state' changes",
"interval": "interval, time between switch updates in seconds",
"max_brightness": "max_brightness, in %",
"max_color_temp": "max_color_temp, in Kelvin",
"min_brightness": "min_brightness, in %",
"min_color_temp": "min_color_temp, in Kelvin",
"only_once": "only_once, only adapt the lights when turning them on",
"prefer_rgb_color": "prefer_rgb_color, use 'rgb_color' over 'color_temp' when possible",
"separate_turn_on_commands": "separate_turn_on_commands, for each attribute (color, brightness, etc.) in 'light.turn_on', required for some lights.",
"sleep_brightness": "sleep_brightness, in %",
"sleep_color_temp": "sleep_color_temp, in Kelvin",
"sunrise_offset": "sunrise_offset, in +/- seconds",
"sunrise_time": "sunrise_time, in 'HH:MM:SS' format (if 'None', it uses the actual sunrise time at your location)",
"sunset_offset": "sunset_offset, in +/- seconds",
"sunset_time": "sunset_time, in 'HH:MM:SS' format (if 'None', it uses the actual sunset time at your location)",
"take_over_control": "take_over_control, if anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
"detect_non_ha_changes": "detect_non_ha_changes, detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
"transition": "transition, in seconds"
"initial_transition": "initial_transition: When lights turn 'off' to 'on'. (seconds)",
"sleep_transition": "sleep_transition: When 'sleep_state' changes. (seconds)",
"interval": "interval: Time between switch updates. (seconds)",
"max_brightness": "max_brightness: Highest brightness of lights during a cycle. (%)",
"max_color_temp": "max_color_temp: Coldest hue of the color temperature cycle. (Kelvin)",
"min_brightness": "min_brightness: Lowest brightness of lights during a cycle. (%)",
"min_color_temp": "min_color_temp, Warmest hue of the color temperature cycle. (Kelvin)",
"only_once": "only_once: Only adapt the lights when turning them on.",
"prefer_rgb_color": "prefer_rgb_color: Use 'rgb_color' rather than 'color_temp' when possible.",
"separate_turn_on_commands": "separate_turn_on_commands: Separate the commands for each attribute (color, brightness, etc.) in 'light.turn_on' (required for some lights).",
"sleep_brightness": "sleep_brightness, Brightness setting for Sleep Mode. (%)",
"sleep_color_temp": "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)",
"sunrise_offset": "sunrise_offset: How long before(-) or after(+) to define the sunrise point of the cycle (+/- seconds)",
"sunrise_time": "sunrise_time: Manual override of the sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"sunset_offset": "sunset_offset: How long before(-) or after(+) to define the sunset point of the cycle (+/- seconds)",
"sunset_time": "sunset_time: Manual override of the sunset time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"take_over_control": "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
"detect_non_ha_changes": "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
"transition": "Transition time when applying a change to the lights (seconds)"
}
}
},

View file

@ -0,0 +1,49 @@
{
"title": "Kohanduv valgus",
"config": {
"step": {
"user": {
"title": "Vali kohanduva valguse üksuse nimi",
"description": "Igas üksuses võib olla mitu valgustit!",
"data": {
"name": "Nimi"
}
}
},
"abort": {
"already_configured": "Üksus on juba seadistatud"
}
},
"options": {
"step": {
"init": {
"title": "Kohanduva valguse suvandid",
"description": "Kohanduva valguse suvandid. Valikute nimetused ühtuvad YAML kirjes olevatega. Valikuid ei kuvata kui seadistus on tehtud YAML kirjes.",
"data": {
"lights": "valgustid",
"initial_transition": "Algne üleminek kui valgustid lülituvad sisse/välja või unerežiim muutub",
"interval": "Intervall, aeg muutuste vahel sekundites",
"max_brightness": "Suurim heledus %",
"max_color_temp": "Suurim värvustemperatuur Kelvinites",
"min_brightness": "Vähim heledus %",
"min_color_temp": "Vähim värvustemperatuur Kelvinites",
"only_once": "Ainult üks kord, rakendub ainult valgusti sisselülitamisel",
"prefer_rgb_color": "Eelista RGB värve, võimalusel kasuta RGB sätteid värvustemperatuuri asemel",
"separate_turn_on_commands": "Eraldi lülitused iga valiku (värvus, heledus jne.) sisselülitamiseks, mõned valgustid vajavad seda.",
"sleep_brightness": "Unerežiimi heledus %",
"sleep_color_temp": "Uneržiimi värvus Kelvinites",
"sunrise_offset": "Nihe päikesetõusust, +/- sekundit",
"sunrise_time": "Päikesetõusu aeg 'HH:MM:SS' vormingus. (Kui jätta tühjaks kasutatakse asukohajärgset)",
"sunset_offset": "Nihe päikeseloojangust, +/- sekundit",
"sunset_time": "Päikeseloojangu aeg 'HH:MM:SS' vormingus. (Kui jätta tühjaks kasutatakse asukohajärgset)",
"take_over_control": "Käsitsi juhtimine: kui miski peale kohanduva valguse enda lültiab valgusti sisse ja see juba põleb, katkesta kohandamine kuni järgmise välise lülitamiseni.",
"detect_non_ha_changes": "Märka väliseid lülitusi: kui mõni säte muutub üle 10% (isegi väljaspoolt HA juhituna) siis peab käsitsi juhtimine olema lubatud (kutsutakse 'homeassistant.update_entity')'interval'!)",
"transition": "Üleminekud, sekundites"
}
}
},
"error": {
"option_error": "Vigane suvand"
}
}
}

View file

@ -0,0 +1,50 @@
{
"title": "Éclairage adaptatif",
"config": {
"step": {
"user": {
"title": "Choisissez un nom pour cette instance d'éclairage adaptatif",
"description": "Choisissez un nom pour cette instance. Vous pouvez configurer plusieurs instances d'éclairage adaptatif, chacune pouvant contrôler plusieurs lampes !",
"data": {
"name": "Nom"
}
}
},
"abort": {
"already_configured": "Cet appareil est déjà configuré"
}
},
"options": {
"step": {
"init": {
"title": "Options d'éclairage adaptatif",
"description": "Tous les paramètres de l'instance d'éclairage adaptatif. Les noms des options correspondent aux paramètres YAML. Aucune option n'est affichée si l'entrée adaptive_lighting est définie dans votre configuration YAML.",
"data": {
"lights": "lights : Les lampes à contrôler",
"initial_transition": "initial_transition : Transition (en secondes) lorsque l'état d'une lampe passe d'« éteinte » à « allumée ».",
"sleep_transition": "sleep_transition : Transition (en secondes) lorsque « sleep_state » est commuté.",
"interval": "interval : Temps (en secondes) entre deux mises à jour du commutateur.",
"max_brightness": "max_brightness : Luminosité maximale des lampes (en pourcentage) au cours d'un cycle.",
"max_color_temp": "max_color_temp : Couleur la plus froide (en kelvins) du cycle de température de couleur.",
"min_brightness": "min_brightness : Luminosité minimale des lampes (en pourcentage) au cours d'un cycle.",
"min_color_temp": "min_color_temp : Couleur la plus chaude (en kelvins) du cycle de température de couleur.",
"only_once": "only_once : Adapter les lampes uniquement au moment où elles sont allumées.",
"prefer_rgb_color": "prefer_rgb_color : Utiliser « rgb_color » plutôt que « color_temp » lorsque cela est possible.",
"separate_turn_on_commands": "separate_turn_on_commands : Séparer les commandes pour chaque attribut (couleur, luminosité, etc.) de « light.turn_on » (nécessaire pour certaines lampes).",
"sleep_brightness": "sleep_brightness : Luminosité (en pourcentage) du mode nuit.",
"sleep_color_temp": "sleep_color_temp : Température de couleur (en kelvins) du mode nuit.",
"sunrise_offset": "sunrise_offset : Décalage (en secondes [- : passé, + : futur]) du cycle par rapport au lever du soleil.",
"sunrise_time": "sunrise_time : Heure (HH:MM:SS) du lever du soleil. Si « None », utilise l'heure correspondant à votre emplacement.",
"sunset_offset": "sunset_offset : Décalage (en secondes [- : passé, + : futur]) du cycle par rapport au coucher du soleil.",
"sunset_time": "sunset_time : Heure (HH:MM:SS) du coucher du soleil. Si « None », utilise l'heure correspondant à votre emplacement.",
"take_over_control": "take_over_control : Si quelque chose d'autre que l'éclairage adaptatif appelle « light.turn_on » alors qu'une lampe est déjà allumée, cesser d'adapter cette lampe jusqu'à ce qu'elle (ou le commutateur) soit éteinte puis rallumée.",
"detect_non_ha_changes": "detect_non_ha_changes : Détecter tout changement de plus de 10 % appliqué aux lampes (même en dehors de HA). Nécessite que « take_over_control » soit activé. (Appelle « homeassistant.update_entity » tous les « interval » !)",
"transition": "transition : Durée de la transition (en secondes) des changements appliqués aux lampes."
}
}
},
"error": {
"option_error": "Option non valide"
}
}
}

View file

@ -0,0 +1,49 @@
{
"title":"Adaptiv Belysning",
"config":{
"step":{
"user":{
"title":"Velg et navn",
"description":"Velg et navn for denne konfigurasjonen for adaptiv belysning - hver konfigurasjon kan inneholde flere lyskilder!",
"data":{
"name":"Navn"
}
}
},
"abort":{
"already_configured":"Denne enheten er allerede konfigurert!"
}
},
"options":{
"step":{
"init":{
"title":"Adaptiv Belysning Innstillinger",
"description":"Alle innstillinger for en adaptiv belysning konfigurasjon. Innstillingene er identiske med innstillingene for YAML konfigurasjon. Ingen innstillinger vises dersom du har definert adaptive_lighting i din YAML konfigurasjon.",
"data":{
"lights":"Lys / Lyskilder",
"initial_transition":"'initial_transition': overgangen (i sekunder) når lysene skrus av eller på - eller når 'sleep_state' endres",
"interval":"'interval': tiden mellom oppdateringer (i sekunder)",
"max_brightness":"'max_brightness': den høyeste lysstyrken (i prosent) på lysene i løpet av en syklus",
"max_color_temp":"'max_color_temp': den høyeste fargetemperaturen (i kelvin) på lysene i løpet av en syklus",
"min_brightness":"'min_brightness': den laveste lysstyrken (i prosent) på lysene i løpet av en syklus",
"min_color_temp":"'min_color_temp': den laveste fargetemperaturen (i kelvin) på lysene i løpet av en syklus",
"only_once":"'only_once': anvend innstillingene for adaptiv belysning kun når lysene skrus av eller på",
"prefer_rgb_color":"'prefer_rgb_color': benytt rgb i stedet for fargetemperatur dersom det er mulig",
"separate_turn_on_commands":"'separate_turn_on_commands': separer kommandone i 'light.turn_on' for hver attributt (farge, lysstyrke, osv.). Dette kan være nødvendig for enkelte typer lys / lyskilder",
"sleep_brightness":"'sleep_brightness': lysstyrken på lysene (i prosent) når 'sleep_mode' (søvnmodus) er aktiv",
"sleep_color_temp":"'sleep_color_temp': fargetemperaturen på lysene (i kelvin) når 'sleep_mode' (søvnmodus) er aktiv",
"sunrise_offset":"'sunrise_offset': hvor lenge før (-) eller etter (+) tidspunktet solen står opp (lokalt) skal defineres som soloppgang (i sekunder)",
"sunrise_time":"'sunrise_time': definer tidspunktet for soloppgang manuelt (i følgende format: TT:MM:SS)",
"sunset_offset":"'sunset_offset': hvor lenge før (-) eller etter (+) tidspunktet solen går ned (lokalt) skal defineres som solnedgang (i sekunder)",
"sunset_time":"'sunset_time': definer tidspunktet for solnedgang manuelt (i følgende format: TT:MM:SS - f. eks: '20:30:00' vil definere tidspunktet for solnegang som halv-ni på kvelden)",
"take_over_control":"'take_over_control': dersom en annen tjeneste enn adaptiv belysning skrur lysene av eller på, vil automatisk adaptering av lyset stoppes inntil lyset (eller den tilhørende bryteren for adaptiv belysning) blir slått av - og på igjen",
"detect_non_ha_changes":"'detect_non_ha_changes': registrerer alle endringer i lysstyrke over 10% med opprinnelse utenfor Home Assistant - krever at 'take_over_control' er aktivert (OBS: tilkaller 'homeassistant.update_entity' ved hvert 'interval'!)",
"transition":"'transition': varigheten (i sekunder) på overgangen når lysene oppdateres "
}
}
},
"error":{
"option_error":"En eller flere valgte innstillinger er ugyldige"
}
}
}

View file

@ -0,0 +1,50 @@
{
"title": "Adaptacyjne oświetlenie",
"config": {
"step": {
"user": {
"title": "Wybierz nazwę grupy dla Adaptacyjnego oświetlenia",
"description": "Wybierz nazwę dla grupy. Możesz użyć wiele grup Adaptacyjnego oświetlenia, każda może mieć dowolną konfigurację świateł!",
"data": {
"name": "Nazwa"
}
}
},
"abort": {
"already_configured": "Już skonfigurowane!"
}
},
"options": {
"step": {
"init": {
"title": "Adaptacyjne oświetlenie opcje",
"description": "Wszystkie ustawienia dla Adaptacyjnego oświetlenia. Nazwy opcji odpowiadają ustawieniom YAML. Żadne opcje nie są wyświetlane, jeśli masz wpis adaptive_lighting zdefiniowany w konfiguracji YAML.",
"data": {
"lights": "światła",
"initial_transition": "initial_transition: When lights turn 'off' to 'on'. (sekund)",
"sleep_transition": "sleep_transition: When 'sleep_state' changes. (sekund)",
"interval": "interval: Time between switch updates. (sekund)",
"max_brightness": "max_brightness: Highest brightness of lights during a cycle. (%)",
"max_color_temp": "max_color_temp: Coldest hue of the color temperature cycle. (Kelvin)",
"min_brightness": "min_brightness: Lowest brightness of lights during a cycle. (%)",
"min_color_temp": "min_color_temp, Warmest hue of the color temperature cycle. (Kelvin)",
"only_once": "only_once: Only adapt the lights when turning them on.",
"prefer_rgb_color": "prefer_rgb_color: Use 'rgb_color' rather than 'color_temp' when possible.",
"separate_turn_on_commands": "separate_turn_on_commands: Separate the commands for each attribute (color, brightness, etc.) in 'light.turn_on' (required for some lights).",
"sleep_brightness": "sleep_brightness, Brightness setting for Sleep Mode. (%)",
"sleep_color_temp": "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)",
"sunrise_offset": "sunrise_offset: How long before(-) or after(+) to define the sunrise point of the cycle (+/- sekund)",
"sunrise_time": "sunrise_time: Manual override of the sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"sunset_offset": "sunset_offset: How long before(-) or after(+) to define the sunset point of the cycle (+/- sekund)",
"sunset_time": "sunset_time: Manual override of the sunset time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"take_over_control": "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
"detect_non_ha_changes": "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
"transition": "Transition time when applying a change to the lights (sekund)"
}
}
},
"error": {
"option_error": "Błędne opcje"
}
}
}

View file

@ -0,0 +1,50 @@
{
"title": "Iluminação Adaptativa",
"config": {
"step": {
"user": {
"title": "Escolha um nome para a instância da Iluminação Adaptativa",
"description": "Escolha um nome para esta instância. Você pode executar várias instâncias de iluminação adaptativa, cada uma delas pode conter várias luzes!",
"data": {
"name": "Nome"
}
}
},
"abort": {
"already_configured": "Este dispositivo já está configurado"
}
},
"options": {
"step": {
"init": {
"title": "Opções da iluminação adaptiva",
"description": "Todas as configurações de um componente de iluminação adaptativa. Os nomes das opções correspondem às configurações de YAML. Nenhuma opção será exibida se você tiver a entrada adaptive_lighting definida em sua configuração YAML.",
"data": {
"lights": "luzes",
"initial_transition": "initial_transition: Quando as luzes mudam de 'off' para 'on'. (segundos)",
"sleep_transition": "sleep_transition: Quando 'sleep_state' muda. (segundos)",
"interval": "interval: Tempo entre as atualizações do switch. (segundos)",
"max_brightness": "max_brightness: Maior brilho das luzes durante um ciclo. (%)",
"max_color_temp": "max_color_temp: Matiz mais frio do ciclo de temperatura de cor. (Kelvin)",
"min_brightness": "min_brightness: Menor brilho das luzes durante um ciclo. (%)",
"min_color_temp": "min_color_temp, matiz mais quente do ciclo de temperatura de cor. (Kelvin)",
"only_once": "only_once: Apenas adapte as luzes ao ligá-las.",
"prefer_rgb_color": "prefer_rgb_color: Use 'rgb_color' em vez de 'color_temp' quando possível.",
"separate_turn_on_commands": "separar_turn_on_commands: Separe os comandos para cada atributo (cor, brilho, etc.) em 'light.turn_on' (necessário para algumas luzes).",
"sleep_brightness": "sleep_brightness, configuração de brilho para o modo de suspensão. (%)",
"sleep_color_temp": "sleep_color_temp: configuração de temperatura de cor para o modo de suspensão. (Kelvin)",
"sunrise_offset": "sunrise_offset: Quanto tempo antes (-) ou depois (+) para definir o ponto do nascer do sol do ciclo (+/- segundos)",
"sunrise_time": "sunrise_time: substituição manual do horário do nascer do sol, se 'Nenhum', ele usa o horário real do nascer do sol em sua localização (HH:MM:SS)",
"sunset_offset": "Sunset_offset: Quanto tempo antes (-) ou depois (+) para definir o ponto de pôr do sol do ciclo (+/- segundos)",
"sunset_time": "sunset_time: substituição manual do horário do pôr do sol, se 'Nenhum', ele usa o horário real do nascer do sol em sua localização (HH:MM:SS)",
"take_over_control": "take_over_control: Se qualquer coisa, exceto Adaptive Lighting, chamar 'light.turn_on' quando uma luz já estiver acesa, pare de adaptar essa luz até que ela (ou o interruptor) desligue -> ligue.",
"detect_non_ha_changes": "detect_non_ha_changes: detecta todas as alterações > 10% feitas nas luzes (também fora do HA), requer que 'take_over_control' seja ativado (chama 'homeassistant.update_entity' a cada 'intervalo'!)",
"transition": "Tempo de transição ao aplicar uma mudança nas luzes (segundos)"
}
}
},
"error": {
"option_error": "Opção inválida"
}
}
}

View file

@ -0,0 +1,49 @@
{
"title": "Адаптивне освітлення",
"config": {
"step": {
"user": {
"title": "Оберіть ім’я для екземпляра адаптивного освітлення",
"description": "Оберіть ім’я для цього екземпляра. Ви можете мати декілька екземплярів адаптивного освітлення, кожен може містити декілька приладів!",
"data": {
"name": "Ім’я"
}
}
},
"abort": {
"already_configured": "Цей пристрій вже налаштовано"
}
},
"options": {
"step": {
"init": {
"title": "Опції адаптивного освітлення",
"description": "Всі налаштування компонента адаптивного освітлення. Назви опцій відповідають налаштуванням у YAML. Опції не відображаються, якщо ви вже визначили їх у компоненті adaptive_lighting вашої YAML-конфігурації.",
"data": {
"lights": "прилади",
"initial_transition": "initial_transition: Коли прилад вимикається (off), вмикається (on), або змінює 'sleep_state'. (секунди)",
"interval": "interval: Час між оновленнями перемикача. (секунди)",
"max_brightness": "max_brightness: Найвища яскравість світла під час циклу. (%)",
"max_color_temp": "max_color_temp: Найхолодніший відтінок циклу кольорової температури. (Кельвін)",
"min_brightness": "min_brightness: Найнижча яскравість світла під час циклу. (%)",
"min_color_temp": "min_color_temp: Найтепліший відтінок циклу кольорової температури. (%)",
"only_once": "only_once: Адаптувати світло лише після початкового увімкнення.",
"prefer_rgb_color": "prefer_rgb_color: Використовувати 'rgb_color' замість 'color_temp', коли можливо.",
"separate_turn_on_commands": "separate_turn_on_commands: Окремі команди для кожного атрибута (колір, яскравість, тощо.) в 'light.turn_on' (необхідні для деяких приладів).",
"sleep_brightness": "sleep_brightness: Налаштування яскравості для Режиму сну. (%)",
"sleep_color_temp": "sleep_color_temp: Температура кольору для Режиму сну. (Кельвін)",
"sunrise_offset": "sunrise_offset: Як за довго до(-) або після(+) визначати точку сходу сонця для циклу (+/- секунд)",
"sunrise_time": "sunrise_time: Ручний перезапис часу сходу сонця, якщо 'None', тоді використовується час сходу сонця у вашій локації (HH:MM:SS)",
"sunset_offset": "sunset_offset: Як за довго до(-) або після(+) визначати точку заходу сонця для циклу (+/- секунд)",
"sunset_time": "sunset_time: Ручний перезапис часу заходу сонця, якщо 'None', тоді використовується час заходу сонця у вашій локації (HH:MM:SS)",
"take_over_control": "take_over_control: Якщо що-небудь, окрім Адаптивного освітлення, викликає 'light.turn_on', коли світло вже увімкнено, чи адаптувати освітлення допоки світло (або перемикач) перемкнеться (off -> on).",
"detect_non_ha_changes": "detect_non_ha_changes: виявляти всі зміни >10% до освітлення (включаючи ті, що зроблені поза HA), вимагає, щоб 'take_over_control' був включений (виклик 'homeassistant.update_entity' кожного оновлення 'interval'!)",
"transition": "Час переходу, який застосовується до освітлення (секунди)"
}
}
},
"error": {
"option_error": "Хибна опція"
}
}
}