Merge branch 'master' into accent_colors

This commit is contained in:
Michael Kirsch 2021-07-08 20:25:11 +02:00
commit cf29d5084f
8 changed files with 432 additions and 71 deletions

View file

@ -5,5 +5,6 @@
"config_flow": true,
"dependencies": [],
"codeowners": ["@basnijholt"],
"version": "1.0.0",
"requirements": []
}

View file

@ -38,7 +38,13 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE,
VALID_TRANSITION,
is_on,
COLOR_MODE_RGB,
COLOR_MODE_RGBW,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_BRIGHTNESS,
ATTR_SUPPORTED_COLOR_MODES,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@ -201,25 +207,23 @@ def is_our_context(context: Optional[Context]) -> bool:
return context.id.startswith(_DOMAIN_SHORT)
def _copy_and_pop(dct, keys):
"""Copy a dictionary and remove 'keys' if they exist."""
copy = dct.copy()
for key in keys:
copy.pop(key, None)
return copy
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_datas.append(
_copy_and_pop(service_data, (ATTR_WHITE_VALUE, ATTR_BRIGHTNESS))
)
service_data_color = service_data.copy()
service_data_color.pop(ATTR_WHITE_VALUE, None)
service_data_color.pop(ATTR_BRIGHTNESS, None)
service_datas.append(service_data_color)
if adapt_brightness:
service_datas.append(
_copy_and_pop(service_data, (ATTR_RGB_COLOR, ATTR_COLOR_TEMP))
)
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)
return service_datas
@ -275,25 +279,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"),
)
@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(
@ -432,7 +447,24 @@ 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_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(
@ -594,10 +626,17 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._transition = min(
data[CONF_TRANSITION], self._interval.total_seconds() // 2
)
_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],
@ -856,6 +895,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,
@ -864,12 +904,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
):
return
self.turn_on_off_listener.last_service_data[light] = service_data
service_datas = (
_split_service_data(service_data, adapt_brightness, adapt_color)
if self._separate_turn_on_commands
else [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'",
@ -884,6 +920,20 @@ 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,
@ -930,6 +980,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,
@ -1124,7 +1175,10 @@ class SunLightSettings:
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
location = self.astral_location
@ -1141,8 +1195,14 @@ 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
@ -1327,7 +1387,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
@ -1406,6 +1466,7 @@ class TurnOnOffListener:
def is_manually_controlled(
self,
switch: AdaptiveSwitch,
light: str,
force: bool,
adapt_brightness: bool,
@ -1430,7 +1491,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"
@ -1443,6 +1504,7 @@ class TurnOnOffListener:
async def significant_change(
self,
switch: AdaptiveSwitch,
light: str,
adapt_brightness: bool,
adapt_color: bool,
@ -1501,7 +1563,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(

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

@ -3,42 +3,42 @@
"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' or 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. (%)",
"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,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": "Хибна опція"
}
}
}