mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-12 14:54:04 +02:00
start with YAML config validation
This commit is contained in:
parent
cadd8279b9
commit
3b275e1dd3
4 changed files with 112 additions and 90 deletions
|
|
@ -29,15 +29,36 @@ Technical notes: I had to make a lot of assumptions when writing this app
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
|
||||
from .const import DOMAIN, UNDO_UPDATE_LISTENER
|
||||
from .const import CONF_NAME, DOMAIN, UNDO_UPDATE_LISTENER, get_domain_schema
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["switch"]
|
||||
|
||||
|
||||
def _all_unique_profiles(value):
|
||||
"""Validate that all enties have a unique profile name."""
|
||||
hosts = [device[CONF_NAME] for device in value]
|
||||
schema = vol.Schema(vol.Unique())
|
||||
schema(hosts)
|
||||
return value
|
||||
|
||||
|
||||
_DOMAIN_SCHEMA = get_domain_schema(with_fake_none=False)
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.All(
|
||||
cv.ensure_list, [vol.Schema(_DOMAIN_SCHEMA)], _all_unique_profiles
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Import integration from config."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,18 @@
|
|||
"""Config flow for Coronavirus integration."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from copy import copy
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.light import VALID_TRANSITION
|
||||
from homeassistant.core import callback
|
||||
|
||||
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_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,
|
||||
EXTRA_VALIDATION,
|
||||
FAKE_NONE,
|
||||
VALIDATION_TUPLES,
|
||||
get_domain_schema,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
@ -71,6 +38,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, user_input=None):
|
||||
"""Handle configuration by yaml file."""
|
||||
_DOMAIN_SCHEMA = get_domain_schema(with_fake_none=True)
|
||||
schema = {k: v for k, v in _DOMAIN_SCHEMA.items() if k in user_input}
|
||||
vol.Schema(schema)(user_input)
|
||||
_LOGGER.error(str(user_input) + str(schema))
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
|
|
@ -78,6 +53,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
def validate_options(user_input, errors):
|
||||
for key, validate in EXTRA_VALIDATION.items():
|
||||
# these are unserializable validators
|
||||
try:
|
||||
value = user_input.get(key)
|
||||
if value is not None and value != FAKE_NONE:
|
||||
validate(user_input[key])
|
||||
except vol.Invalid:
|
||||
_LOGGER.exception("Configuration option %s=%s is incorrect", key, value)
|
||||
errors["base"] = "option_error"
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle a option flow for Adaptive Lighting."""
|
||||
|
||||
|
|
@ -89,46 +76,16 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
"""Handle options flow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
for key, validate in EXTRA_VALIDATION:
|
||||
# these are unserializable validators
|
||||
try:
|
||||
value = user_input.get(key)
|
||||
if value is not None and value != FAKE_NONE:
|
||||
validate(user_input[key])
|
||||
except vol.Invalid:
|
||||
_LOGGER.exception(
|
||||
"Configuration option %s=%s is incorrect", key, value
|
||||
)
|
||||
errors["base"] = "option_error"
|
||||
validate_options(user_input, errors)
|
||||
if not errors:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
options = self.config_entry.options
|
||||
int_between = lambda a, b: vol.All(vol.Coerce(int), vol.Range(min=a, max=b))
|
||||
all_lights = cv.multi_select(self.hass.states.async_entity_ids("light"))
|
||||
validation_tuples = [
|
||||
(CONF_LIGHTS, DEFAULT_LIGHTS, all_lights),
|
||||
(CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST, bool),
|
||||
(CONF_DISABLE_ENTITY, FAKE_NONE, str),
|
||||
(CONF_DISABLE_STATE, FAKE_NONE, str),
|
||||
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
|
||||
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
|
||||
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE, bool),
|
||||
(CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_SLEEP_ENTITY, FAKE_NONE, str),
|
||||
(CONF_SLEEP_STATE, FAKE_NONE, str),
|
||||
(CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET, int),
|
||||
(CONF_SUNRISE_TIME, FAKE_NONE, str),
|
||||
(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int),
|
||||
(CONF_SUNSET_TIME, FAKE_NONE, str),
|
||||
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
|
||||
]
|
||||
validation_tuples = copy(VALIDATION_TUPLES)
|
||||
lights_tuple = (*validation_tuples[0][:-1], all_lights)
|
||||
validation_tuples[0] = lights_tuple
|
||||
|
||||
options = self.config_entry.options
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(key, default=options.get(key, default)): validation
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.components.light import VALID_TRANSITION
|
||||
|
||||
ICON = "mdi:theme-light-dark"
|
||||
|
||||
|
|
@ -36,14 +36,60 @@ CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 60
|
|||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
FAKE_NONE = "None" # TODO: use `from homeassistant.const import ENTITY_MATCH_NONE`?
|
||||
|
||||
EXTRA_VALIDATION = [ # these validators cannot be serialized
|
||||
(CONF_DISABLE_ENTITY, cv.entity_id),
|
||||
(CONF_DISABLE_STATE, vol.All(cv.ensure_list_csv, [cv.string])),
|
||||
(CONF_INTERVAL, cv.time_period),
|
||||
(CONF_SLEEP_ENTITY, cv.entity_id),
|
||||
(CONF_SLEEP_STATE, vol.All(cv.ensure_list_csv, [cv.string])),
|
||||
(CONF_SUNRISE_OFFSET, cv.time_period),
|
||||
(CONF_SUNRISE_TIME, cv.time),
|
||||
(CONF_SUNSET_OFFSET, cv.time_period),
|
||||
(CONF_SUNSET_TIME, cv.time),
|
||||
|
||||
def int_between(a, b):
|
||||
return vol.All(vol.Coerce(int), vol.Range(min=a, max=b))
|
||||
|
||||
|
||||
VALIDATION_TUPLES = [
|
||||
(CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids),
|
||||
(CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST, bool),
|
||||
(CONF_DISABLE_ENTITY, FAKE_NONE, str),
|
||||
(CONF_DISABLE_STATE, FAKE_NONE, str),
|
||||
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
|
||||
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
|
||||
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_ONLY_ONCE, DEFAULT_ONLY_ONCE, bool),
|
||||
(CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP, int_between(1000, 10000)),
|
||||
(CONF_SLEEP_ENTITY, FAKE_NONE, str),
|
||||
(CONF_SLEEP_STATE, FAKE_NONE, str),
|
||||
(CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET, int),
|
||||
(CONF_SUNRISE_TIME, FAKE_NONE, str),
|
||||
(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int),
|
||||
(CONF_SUNSET_TIME, FAKE_NONE, str),
|
||||
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
|
||||
]
|
||||
|
||||
EXTRA_VALIDATION = { # these validators cannot be serialized
|
||||
CONF_DISABLE_ENTITY: cv.entity_id,
|
||||
CONF_DISABLE_STATE: vol.All(cv.ensure_list_csv, [cv.string]),
|
||||
CONF_INTERVAL: cv.time_period,
|
||||
CONF_SLEEP_ENTITY: cv.entity_id,
|
||||
CONF_SLEEP_STATE: vol.All(cv.ensure_list_csv, [cv.string]),
|
||||
CONF_SUNRISE_OFFSET: cv.time_period,
|
||||
CONF_SUNRISE_TIME: cv.time,
|
||||
CONF_SUNSET_OFFSET: cv.time_period,
|
||||
CONF_SUNSET_TIME: cv.time,
|
||||
}
|
||||
|
||||
|
||||
def get_domain_schema(with_fake_none=False):
|
||||
validation_tuples = [
|
||||
(key, default, EXTRA_VALIDATION.get(key, validation))
|
||||
for key, default, validation in VALIDATION_TUPLES
|
||||
]
|
||||
validation_tuples.append((CONF_NAME, DEFAULT_NAME, cv.string))
|
||||
|
||||
def replace_none(x):
|
||||
if not with_fake_none and x == FAKE_NONE:
|
||||
return vol.UNDEFINED
|
||||
return x
|
||||
|
||||
return {
|
||||
vol.Optional(key, default=replace_none(default)): validation
|
||||
for key, default, validation in validation_tuples
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
# CHECK OUT THE VIZIO COMPONENT!
|
||||
"""Adaptive Lighting Component for Home-Assistant."""
|
||||
|
||||
import asyncio
|
||||
import bisect
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from datetime import timedelta
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
|
@ -128,11 +126,11 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
self._entity_id = f"switch.{DOMAIN}_{slugify(name)}"
|
||||
self._icon = ICON
|
||||
|
||||
data = {**config_entry.options, **config_entry.data}
|
||||
opts = { # replace "None" -> None
|
||||
key: value if value != FAKE_NONE else None
|
||||
for key, value in config_entry.options.items()
|
||||
key: value if value != FAKE_NONE else None for key, value in data.items()
|
||||
}
|
||||
for key, validate in EXTRA_VALIDATION: # Fix the types of the inputs
|
||||
for key, validate in EXTRA_VALIDATION.items(): # Fix the types of the inputs
|
||||
value = opts.get(key)
|
||||
if value is not None:
|
||||
opts[key] = validate(value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue