This commit is contained in:
Bas Nijholt 2020-09-19 11:10:54 +02:00
commit 9d06be0883
4 changed files with 463 additions and 142 deletions

View file

@ -1 +1,206 @@
"""Adaptive Lighting Component for Home-Assistant."""
"""
Adaptive Lighting Component for Home-Assistant.
This component calculates color temperature and brightness to synchronize
your color changing lights with perceived color temperature of the sky throughout
the day. This gives your environment a more natural feel, with cooler whites during
the midday and warmer tints near twilight and dawn.
In addition, the component sets your lights to a nice warm white at 1% in "Sleep" mode,
which is far brighter than starlight but won't reset your adaptive rhythm or break down
too much rhodopsin in your eyes.
Human circadian rhythms are heavily influenced by ambient light levels and
hues. Hormone production, brainwave activity, mood and wakefulness are
just some of the cognitive functions tied to cyclical natural light.
http://en.wikipedia.org/wiki/Zeitgeber
Here's some further reading:
http://www.cambridgeincolour.com/tutorials/sunrise-sunset-calculator.htm
http://en.wikipedia.org/wiki/Color_temperature
Technical notes: I had to make a lot of assumptions when writing this app
* There are no considerations for weather or altitude, but does use your
hub's location to calculate the sun position.
* The component doesn't calculate a true "Blue Hour" -- it just sets the
lights to 2700K (warm white) until your hub goes into Night mode
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.light import (
ATTR_BRIGHTNESS_PCT,
ATTR_COLOR_TEMP,
ATTR_RGB_COLOR,
ATTR_TRANSITION,
)
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.light import (
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_TRANSITION,
VALID_TRANSITION,
is_on,
)
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_NAME,
SERVICE_TURN_ON,
STATE_ON,
SUN_EVENT_SUNRISE,
SUN_EVENT_SUNSET,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_state_change,
async_track_time_interval,
)
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.sun import get_astral_location
from homeassistant.util import slugify
from homeassistant.util.color import (
color_RGB_to_xy,
color_temperature_kelvin_to_mired,
color_temperature_to_rgb,
color_xy_to_hs,
)
from .const import (
CONF_DISABLE_BRIGHTNESS_ADJUST,
CONF_DISABLE_ENTITY,
CONF_DISABLE_STATE,
CONF_INITIAL_TRANSITION,
CONF_INTERVAL,
CONF_LIGHTS,
CONF_MAX_BRIGHTNESS,
CONF_MAX_COLOR_TEMP,
CONF_MIN_BRIGHTNESS,
CONF_MIN_COLOR_TEMP,
CONF_ONLY_ONCE,
CONF_SLEEP_BRIGHTNESS,
CONF_SLEEP_COLOR_TEMP,
CONF_SLEEP_ENTITY,
CONF_SLEEP_STATE,
CONF_SUNRISE_OFFSET,
CONF_SUNRISE_TIME,
CONF_SUNSET_OFFSET,
CONF_SUNSET_TIME,
CONF_NAME,
DEFAULT_NAME,
CONF_TRANSITION,
DEFAULT_DISABLE_BRIGHTNESS_ADJUST,
DEFAULT_INITIAL_TRANSITION,
DEFAULT_INTERVAL,
DEFAULT_LIGHTS,
DEFAULT_MAX_BRIGHTNESS,
DEFAULT_MAX_COLOR_TEMP,
DEFAULT_MIN_BRIGHTNESS,
DEFAULT_MIN_COLOR_TEMP,
DEFAULT_ONLY_ONCE,
DEFAULT_SLEEP_BRIGHTNESS,
DEFAULT_SLEEP_COLOR_TEMP,
DEFAULT_SUNRISE_OFFSET,
DEFAULT_SUNSET_OFFSET,
DEFAULT_TRANSITION,
DOMAIN,
ICON,
SUN_EVENT_MIDNIGHT,
SUN_EVENT_NOON,
)
_SUPPORT_OPTS = {
"brightness": SUPPORT_BRIGHTNESS,
"color_temp": SUPPORT_COLOR_TEMP,
"color": SUPPORT_COLOR,
"transition": SUPPORT_TRANSITION,
}
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_LIGHTS, default=DEFAULT_LIGHTS): cv.entity_ids,
vol.Optional(
CONF_DISABLE_BRIGHTNESS_ADJUST,
default=DEFAULT_DISABLE_BRIGHTNESS_ADJUST,
): cv.boolean,
vol.Optional(CONF_DISABLE_ENTITY): cv.entity_id,
vol.Optional(CONF_DISABLE_STATE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION
): VALID_TRANSITION,
vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.time_period,
vol.Optional(
CONF_MAX_BRIGHTNESS, default=DEFAULT_MAX_BRIGHTNESS
): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(
CONF_MAX_COLOR_TEMP, default=DEFAULT_MAX_COLOR_TEMP
): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(
CONF_MIN_BRIGHTNESS, default=DEFAULT_MIN_BRIGHTNESS
): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(
CONF_MIN_COLOR_TEMP, default=DEFAULT_MIN_COLOR_TEMP
): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(CONF_ONLY_ONCE, default=DEFAULT_ONLY_ONCE): cv.boolean,
vol.Optional(
CONF_SLEEP_BRIGHTNESS, default=DEFAULT_SLEEP_BRIGHTNESS
): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(
CONF_SLEEP_COLOR_TEMP, default=DEFAULT_SLEEP_COLOR_TEMP
): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(CONF_SLEEP_ENTITY): cv.entity_id,
vol.Optional(CONF_SLEEP_STATE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_SUNRISE_OFFSET, default=DEFAULT_SUNRISE_OFFSET
): cv.time_period,
vol.Optional(CONF_SUNRISE_TIME): cv.time,
vol.Optional(
CONF_SUNSET_OFFSET, default=DEFAULT_SUNSET_OFFSET
): cv.time_period,
vol.Optional(CONF_SUNSET_TIME): cv.time,
vol.Optional(
CONF_TRANSITION, default=DEFAULT_TRANSITION
): VALID_TRANSITION,
}
)
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass, config):
"""Import integration from config."""
if DOMAIN in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
)
return True
async def async_setup_entry(hass, config_entry):
"""Set up the component."""
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, "switch")
)
return True

View file

@ -5,9 +5,46 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
from homeassistant.components.light import VALID_TRANSITION
from homeassistant.core import callback
from .const import DOMAIN, _convert_to_options_schema
from .const import ( # _convert_to_options_schema,
CONF_DISABLE_BRIGHTNESS_ADJUST,
CONF_DISABLE_ENTITY,
CONF_DISABLE_STATE,
CONF_INITIAL_TRANSITION,
CONF_INTERVAL,
CONF_LIGHTS,
CONF_MAX_BRIGHTNESS,
CONF_MAX_COLOR_TEMP,
CONF_MIN_BRIGHTNESS,
CONF_MIN_COLOR_TEMP,
CONF_ONLY_ONCE,
CONF_SLEEP_BRIGHTNESS,
CONF_SLEEP_COLOR_TEMP,
CONF_SLEEP_ENTITY,
CONF_SLEEP_STATE,
CONF_SUNRISE_OFFSET,
CONF_SUNRISE_TIME,
CONF_SUNSET_OFFSET,
CONF_SUNSET_TIME,
CONF_TRANSITION,
DEFAULT_DISABLE_BRIGHTNESS_ADJUST,
DEFAULT_INITIAL_TRANSITION,
DEFAULT_INTERVAL,
DEFAULT_LIGHTS,
DEFAULT_MAX_BRIGHTNESS,
DEFAULT_MAX_COLOR_TEMP,
DEFAULT_MIN_BRIGHTNESS,
DEFAULT_MIN_COLOR_TEMP,
DEFAULT_ONLY_ONCE,
DEFAULT_SLEEP_BRIGHTNESS,
DEFAULT_SLEEP_COLOR_TEMP,
DEFAULT_SUNRISE_OFFSET,
DEFAULT_SUNSET_OFFSET,
DEFAULT_TRANSITION,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
@ -51,8 +88,79 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
options_schema = _convert_to_options_schema(
self.hass, self.config_entry.options
options = self.config_entry.options
lights = options.get(CONF_LIGHTS, DEFAULT_LIGHTS)
disable_brightness_adjust = options.get(
CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST
)
disable_entity = options.get(CONF_DISABLE_ENTITY)
disable_state = options.get(CONF_DISABLE_STATE)
initial_transition = options.get(
CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION
)
interval = options.get(CONF_INTERVAL, DEFAULT_INTERVAL)
max_brightness = options.get(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS)
max_color_temp = options.get(CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP)
min_brightness = options.get(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS)
min_color_temp = options.get(CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP)
only_once = options.get(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE)
sleep_brightness = options.get(CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS)
sleep_color_temp = options.get(CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP)
sleep_entity = options.get(CONF_SLEEP_ENTITY)
sleep_state = options.get(CONF_SLEEP_STATE)
sunrise_offset = options.get(CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET)
sunrise_time = options.get(CONF_SUNRISE_TIME)
sunset_offset = options.get(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET)
sunset_time = options.get(CONF_SUNSET_TIME)
transition = options.get(CONF_TRANSITION, DEFAULT_TRANSITION)
all_lights = self.hass.states.async_entity_ids("light")
all_lights = cv.multi_select(all_lights)
options_schema = vol.Schema(
{
vol.Optional(CONF_LIGHTS, default=lights): all_lights,
# vol.Optional(
# CONF_DISABLE_BRIGHTNESS_ADJUST, default=disable_brightness_adjust
# ): bool,
# vol.Optional(CONF_DISABLE_ENTITY, default=disable_entity): str,
# vol.Optional(CONF_DISABLE_STATE, default=disable_state): str,
# vol.Optional(
# CONF_INITIAL_TRANSITION, default=initial_transition
# ): cv.positive_int,
# vol.Optional(CONF_INTERVAL, default=interval): cv.positive_int,
# vol.Optional(CONF_MAX_BRIGHTNESS, default=max_brightness): vol.All(
# vol.Coerce(int), vol.Range(min=1, max=100)
# ),
# vol.Optional(CONF_MAX_COLOR_TEMP, default=max_color_temp): vol.All(
# vol.Coerce(int), vol.Range(min=1000, max=10000)
# ),
# vol.Optional(CONF_MIN_BRIGHTNESS, default=min_brightness): vol.All(
# vol.Coerce(int), vol.Range(min=1, max=100)
# ),
# vol.Optional(CONF_MIN_COLOR_TEMP, default=min_color_temp): vol.All(
# vol.Coerce(int), vol.Range(min=1000, max=10000)
# ),
# vol.Optional(CONF_ONLY_ONCE, default=only_once): bool,
# vol.Optional(CONF_SLEEP_BRIGHTNESS, default=sleep_brightness): vol.All(
# vol.Coerce(int), vol.Range(min=1, max=100)
# ),
# vol.Optional(CONF_SLEEP_COLOR_TEMP, default=sleep_color_temp): vol.All(
# vol.Coerce(int), vol.Range(min=1000, max=10000)
# ),
# vol.Optional(CONF_SLEEP_ENTITY, default=sleep_entity): str,
# vol.Optional(CONF_SLEEP_STATE, default=sleep_state): str,
# vol.Optional(CONF_SUNRISE_OFFSET, default=sunrise_offset): int,
# vol.Optional(CONF_SUNRISE_TIME, default=sunrise_time): str,
# vol.Optional(CONF_SUNSET_OFFSET, default=sunset_offset): int,
# vol.Optional(CONF_SUNSET_TIME, default=sunset_time): str,
# vol.Optional(CONF_TRANSITION, default=transition): VALID_TRANSITION,
}
)
# options_schema = _convert_to_options_schema(
# self.hass, self.config_entry.options
# )
return self.async_show_form(step_id="init", data_schema=options_schema)

View file

@ -9,6 +9,7 @@ DOMAIN = "adaptive_lighting"
SUN_EVENT_NOON = "solar_noon"
SUN_EVENT_MIDNIGHT = "solar_midnight"
CONF_NAME, DEFAULT_NAME = "name", "default"
CONF_LIGHTS, DEFAULT_LIGHTS = "lights", []
CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST = (
"disable_brightness_adjust",
@ -36,34 +37,18 @@ CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 60
_COMMON_SCHEMA = {
vol.Optional(CONF_LIGHTS, default=DEFAULT_LIGHTS): cv.entity_ids,
vol.Optional(
CONF_DISABLE_BRIGHTNESS_ADJUST, default=DEFAULT_DISABLE_BRIGHTNESS_ADJUST
): cv.boolean,
vol.Optional(CONF_DISABLE_BRIGHTNESS_ADJUST, default=DEFAULT_DISABLE_BRIGHTNESS_ADJUST): cv.boolean,
vol.Optional(CONF_DISABLE_ENTITY): cv.entity_id,
vol.Optional(CONF_DISABLE_STATE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION
): VALID_TRANSITION,
vol.Optional(CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION): VALID_TRANSITION,
vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.time_period,
vol.Optional(CONF_MAX_BRIGHTNESS, default=DEFAULT_MAX_BRIGHTNESS): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_MAX_COLOR_TEMP, default=DEFAULT_MAX_COLOR_TEMP): vol.All(
vol.Coerce(int), vol.Range(min=1000, max=10000)
),
vol.Optional(CONF_MIN_BRIGHTNESS, default=DEFAULT_MIN_BRIGHTNESS): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_MIN_COLOR_TEMP, default=DEFAULT_MIN_COLOR_TEMP): vol.All(
vol.Coerce(int), vol.Range(min=1000, max=10000)
),
vol.Optional(CONF_MAX_BRIGHTNESS, default=DEFAULT_MAX_BRIGHTNESS): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(CONF_MAX_COLOR_TEMP, default=DEFAULT_MAX_COLOR_TEMP): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(CONF_MIN_BRIGHTNESS, default=DEFAULT_MIN_BRIGHTNESS): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(CONF_MIN_COLOR_TEMP, default=DEFAULT_MIN_COLOR_TEMP): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(CONF_ONLY_ONCE, default=DEFAULT_ONLY_ONCE): cv.boolean,
vol.Optional(CONF_SLEEP_BRIGHTNESS, default=DEFAULT_SLEEP_BRIGHTNESS): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_SLEEP_COLOR_TEMP, default=DEFAULT_SLEEP_COLOR_TEMP): vol.All(
vol.Coerce(int), vol.Range(min=1000, max=10000)
),
vol.Optional(CONF_SLEEP_BRIGHTNESS, default=DEFAULT_SLEEP_BRIGHTNESS): vol.All(vol.Coerce(int), vol.Range(min=1, max=100)),
vol.Optional(CONF_SLEEP_COLOR_TEMP, default=DEFAULT_SLEEP_COLOR_TEMP): vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)),
vol.Optional(CONF_SLEEP_ENTITY): cv.entity_id,
vol.Optional(CONF_SLEEP_STATE): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_SUNRISE_OFFSET, default=DEFAULT_SUNRISE_OFFSET): cv.time_period,
@ -82,22 +67,14 @@ def _convert_to_options_schema(hass, options):
to_type = cv.multi_select(all_lights)
elif value == cv.boolean:
to_type = bool
elif (
isinstance(value, vol.All)
and hasattr(value.validators, "type")
and value.validators[0].type == int
) or value == VALID_TRANSITION:
elif (isinstance(value, vol.All) and hasattr(value.validators, "type") and value.validators[0].type == int) or value == VALID_TRANSITION:
to_type = value
elif value == cv.time_period:
to_type = cv.time_period_dict
else:
to_type = str
default = (
key.default()
if not isinstance(key.default, vol.Undefined)
else vol.UNDEFINED
)
default = key.default() if not isinstance(key.default, vol.Undefined) else vol.UNDEFINED
default = options.get(key.schema, default)
schema[vol.Optional(key.schema, default=default)] = to_type
return vol.Schema(schema)

View file

@ -28,6 +28,7 @@ Technical notes: I had to make a lot of assumptions when writing this app
"""
import asyncio
import bisect
import logging
from datetime import timedelta
@ -35,6 +36,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from custom_components import adaptive_lighting
from homeassistant.components.light import (
ATTR_BRIGHTNESS_PCT,
ATTR_COLOR_TEMP,
@ -74,7 +76,6 @@ from homeassistant.util.color import (
)
from .const import (
_COMMON_SCHEMA,
CONF_DISABLE_BRIGHTNESS_ADJUST,
CONF_DISABLE_ENTITY,
CONF_DISABLE_STATE,
@ -95,6 +96,20 @@ from .const import (
CONF_SUNSET_OFFSET,
CONF_SUNSET_TIME,
CONF_TRANSITION,
DEFAULT_DISABLE_BRIGHTNESS_ADJUST,
DEFAULT_INITIAL_TRANSITION,
DEFAULT_INTERVAL,
DEFAULT_LIGHTS,
DEFAULT_MAX_BRIGHTNESS,
DEFAULT_MAX_COLOR_TEMP,
DEFAULT_MIN_BRIGHTNESS,
DEFAULT_MIN_COLOR_TEMP,
DEFAULT_ONLY_ONCE,
DEFAULT_SLEEP_BRIGHTNESS,
DEFAULT_SLEEP_COLOR_TEMP,
DEFAULT_SUNRISE_OFFSET,
DEFAULT_SUNSET_OFFSET,
DEFAULT_TRANSITION,
DOMAIN,
ICON,
SUN_EVENT_MIDNIGHT,
@ -113,42 +128,15 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
PLATFORM_SCHEMA = vol.Schema(
{
vol.Required(CONF_PLATFORM): DOMAIN,
vol.Optional(CONF_NAME, default="Adaptive Lighting"): cv.string,
**_COMMON_SCHEMA,
}
)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Adaptive Lighting switches."""
switch = AdaptiveSwitch(
hass,
name=config[CONF_NAME],
lights=config[CONF_LIGHTS],
disable_brightness_adjust=config[CONF_DISABLE_BRIGHTNESS_ADJUST],
disable_entity=config.get(CONF_DISABLE_ENTITY),
disable_state=config.get(CONF_DISABLE_STATE),
initial_transition=config[CONF_INITIAL_TRANSITION],
interval=config[CONF_INTERVAL],
max_brightness=config[CONF_MAX_BRIGHTNESS],
max_color_temp=config[CONF_MAX_COLOR_TEMP],
min_brightness=config[CONF_MIN_BRIGHTNESS],
min_color_temp=config[CONF_MIN_COLOR_TEMP],
only_once=config[CONF_ONLY_ONCE],
sleep_brightness=config[CONF_SLEEP_BRIGHTNESS],
sleep_color_temp=config[CONF_SLEEP_COLOR_TEMP],
sleep_entity=config.get(CONF_SLEEP_ENTITY),
sleep_state=config.get(CONF_SLEEP_STATE),
sunrise_offset=config[CONF_SUNRISE_OFFSET],
sunrise_time=config.get(CONF_SUNRISE_TIME),
sunset_offset=config[CONF_SUNSET_OFFSET],
sunset_time=config.get(CONF_SUNSET_TIME),
transition=config[CONF_TRANSITION],
)
add_devices([switch])
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the AdaptiveLighting switch."""
name = config_entry.data[CONF_NAME]
switch = AdaptiveSwitch(hass, name, config_entry)
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
hass.data[DOMAIN][name] = switch
async_add_entities([switch])
def _difference_between_states(from_state, to_state):
@ -191,54 +179,14 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self,
hass,
name,
lights,
disable_brightness_adjust,
disable_entity,
disable_state,
initial_transition,
interval,
max_brightness,
max_color_temp,
min_brightness,
min_color_temp,
only_once,
sleep_brightness,
sleep_color_temp,
sleep_entity,
sleep_state,
sunrise_offset,
sunrise_time,
sunset_offset,
sunset_time,
transition,
config_entry,
):
"""Initialize the Adaptive Lighting switch."""
self.hass = hass
self._name = name
self._entity_id = f"switch.adaptive_lighting_{slugify(name)}"
self._entity_id = f"switch.{DOMAIN}_{slugify(name)}"
self._icon = ICON
# Set attributes from arguments
self._lights = lights
self._disable_brightness_adjust = disable_brightness_adjust
self._disable_entity = disable_entity
self._disable_state = disable_state
self._initial_transition = initial_transition
self._interval = interval
self._max_brightness = max_brightness
self._max_color_temp = max_color_temp
self._min_brightness = min_brightness
self._min_color_temp = min_color_temp
self._only_once = only_once
self._sleep_brightness = sleep_brightness
self._sleep_color_temp = sleep_color_temp
self._sleep_entity = sleep_entity
self._sleep_state = sleep_state
self._sunrise_offset = sunrise_offset
self._sunrise_time = sunrise_time
self._sunset_offset = sunset_offset
self._sunset_time = sunset_time
self._transition = transition
self.config_entry = config_entry
# Initialize attributes that will be set in self._update_attrs
self._percent = None
@ -252,6 +200,104 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
# Set and unset tracker in async_turn_on and async_turn_off
self.unsub_tracker = None
@property
def _lights(self):
return self.config_entry.options.get(CONF_LIGHTS, DEFAULT_LIGHTS)
@property
def _disable_brightness_adjust(self):
return self.config_entry.options.get(
CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST
)
@property
def _disable_entity(self):
return self.config_entry.options.get(CONF_DISABLE_ENTITY)
@property
def _disable_state(self):
return self.config_entry.options.get(CONF_DISABLE_STATE)
@property
def _initial_transition(self):
return self.config_entry.options.get(
CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION
)
@property
def _interval(self):
return self.config_entry.options.get(CONF_INTERVAL, DEFAULT_INTERVAL)
@property
def _max_brightness(self):
return self.config_entry.options.get(
CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS
)
@property
def _max_color_temp(self):
return self.config_entry.options.get(
CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP
)
@property
def _min_brightness(self):
return self.config_entry.options.get(
CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS
)
@property
def _min_color_temp(self):
return self.config_entry.options.get(
CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP
)
@property
def _only_once(self):
return self.config_entry.options.get(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE)
@property
def _sleep_brightness(self):
return self.config_entry.options.get(
CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS
)
@property
def _sleep_color_temp(self):
return self.config_entry.options.get(
CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP
)
@property
def _sleep_entity(self):
return self.config_entry.options.get(CONF_SLEEP_ENTITY)
@property
def _sleep_state(self):
return self.config_entry.options.get(CONF_SLEEP_STATE)
@property
def _sunrise_offset(self):
return self.config_entry.options.get(
CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET
)
@property
def _sunrise_time(self):
return self.config_entry.options.get(CONF_SUNRISE_TIME)
@property
def _sunset_offset(self):
return self.config_entry.options.get(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET)
@property
def _sunset_time(self):
return self.config_entry.options.get(CONF_SUNSET_TIME)
@property
def _transition(self):
return self.config_entry.options.get(CONF_TRANSITION, DEFAULT_TRANSITION)
@property
def entity_id(self):
"""Return the entity ID of the switch."""
@ -411,34 +457,19 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
SUN_EVENT_MIDNIGHT: solar_midnight.timestamp(),
}
def _relevant_events(self, now):
events = []
for days in [-1, 0, 1]:
sun_events = self._get_sun_events(now + timedelta(days=days))
events.extend(list(sun_events.items()))
events = sorted(events, key=lambda x: x[1])
i_now = bisect.bisect([ts for _, ts in events], now.timestamp())
return dict(events[i_now - 2: i_now + 2])
def _calc_percent(self):
now = dt_util.utcnow()
now_ts = now.timestamp()
today = self._get_sun_events(now)
if now_ts < today[SUN_EVENT_SUNRISE]:
# It's before sunrise (after midnight), because it's before
# sunrise (and after midnight) sunset must have happend yesterday.
yesterday = self._get_sun_events(now - timedelta(days=1))
if (
today[SUN_EVENT_MIDNIGHT] > today[SUN_EVENT_SUNSET]
and yesterday[SUN_EVENT_MIDNIGHT] > yesterday[SUN_EVENT_SUNSET]
):
# Solar midnight is after sunset so use yesterdays's time
today[SUN_EVENT_MIDNIGHT] = yesterday[SUN_EVENT_MIDNIGHT]
today[SUN_EVENT_SUNSET] = yesterday[SUN_EVENT_SUNSET]
elif now_ts > today[SUN_EVENT_SUNSET]:
# It's after sunset (before midnight), because it's after sunset
# (and before midnight) sunrise should happen tomorrow.
tomorrow = self._get_sun_events(now + timedelta(days=1))
if (
today[SUN_EVENT_MIDNIGHT] < today[SUN_EVENT_SUNRISE]
and tomorrow[SUN_EVENT_MIDNIGHT] < tomorrow[SUN_EVENT_SUNRISE]
):
# Solar midnight is before sunrise so use tomorrow's time
today[SUN_EVENT_MIDNIGHT] = tomorrow[SUN_EVENT_MIDNIGHT]
today[SUN_EVENT_SUNRISE] = tomorrow[SUN_EVENT_SUNRISE]
today = self._relevant_events(now)
# Figure out where we are in time so we know which half of the
# parabola to calculate. We're generating a different
# sunset-sunrise parabola for before and after solar midnight.