mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-17 17:24:03 +02:00
WIP
This commit is contained in:
parent
909584abbf
commit
9d06be0883
4 changed files with 463 additions and 142 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue