diff --git a/custom_components/adaptive_lighting/__init__.py b/custom_components/adaptive_lighting/__init__.py index c4085dd9..12d70105 100755 --- a/custom_components/adaptive_lighting/__init__.py +++ b/custom_components/adaptive_lighting/__init__.py @@ -26,17 +26,18 @@ Technical notes: I had to make a lot of assumptions when writing this app * 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 import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant.components.light import VALID_TRANSITION -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import ENTRY_STATE_LOADED, SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_NAME from .const import ( CONF_DISABLE_BRIGHTNESS_ADJUST, + UNDO_UPDATE_LISTENER, CONF_DISABLE_ENTITY, CONF_DISABLE_STATE, CONF_INITIAL_TRANSITION, @@ -77,78 +78,118 @@ from .const import ( _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, -) +# 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, +# ) + +PLATFORMS = ["switch"] 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] + for entry in config[DOMAIN]: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry + ) ) - ) return True -async def async_setup_entry(hass, config_entry): +async def async_setup_entry(hass, config_entry: ConfigEntry): """Set up the component.""" + hass.data.setdefault(DOMAIN, {}) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "switch") - ) + undo_listener = config_entry.add_update_listener(async_update_options) + hass.data[DOMAIN][config_entry.entry_id] = { + UNDO_UPDATE_LISTENER: undo_listener + } + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) return True + + +async def async_update_options(hass, config_entry: ConfigEntry): + """Update options.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + +async def async_unload_entry(hass, config_entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS + ] + ) + ) + hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() + + # Exclude this config entry because its not unloaded yet + if not any( + entry.state == ENTRY_STATE_LOADED and entry.entry_id != config_entry.entry_id + for entry in hass.config_entries.async_entries(DOMAIN) + ): + hass.data[DOMAIN].pop(config_entry.entry_id) + + if not hass.data[DOMAIN]: + hass.data.pop(DOMAIN) + + return unload_ok \ No newline at end of file diff --git a/custom_components/adaptive_lighting/config_flow.py b/custom_components/adaptive_lighting/config_flow.py index 5785c0b8..2fb90b4d 100644 --- a/custom_components/adaptive_lighting/config_flow.py +++ b/custom_components/adaptive_lighting/config_flow.py @@ -37,6 +37,7 @@ from .const import ( # _convert_to_options_schema, DEFAULT_MAX_COLOR_TEMP, DEFAULT_MIN_BRIGHTNESS, DEFAULT_MIN_COLOR_TEMP, + UNDO_UPDATE_LISTENER, DEFAULT_ONLY_ONCE, DEFAULT_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_COLOR_TEMP, @@ -121,34 +122,34 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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_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_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_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, diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index cdb3154d..205b689d 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -1,5 +1,6 @@ import voluptuous as vol +from datetime import timedelta import homeassistant.helpers.config_validation as cv from homeassistant.components.light import VALID_TRANSITION @@ -18,7 +19,7 @@ CONF_DISABLE_BRIGHTNESS_ADJUST, DEFAULT_DISABLE_BRIGHTNESS_ADJUST = ( CONF_DISABLE_ENTITY = "disable_entity" CONF_DISABLE_STATE = "disable_state" CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1 -CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90 +CONF_INTERVAL, DEFAULT_INTERVAL = "interval", timedelta(seconds=90) CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 100 CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP = "max_color_temp", 5500 CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS = "min_brightness", 1 @@ -28,12 +29,13 @@ CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS = "sleep_brightness", 1 CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP = "sleep_color_temp", 1000 CONF_SLEEP_ENTITY = "sleep_entity" CONF_SLEEP_STATE = "sleep_state" -CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET = "sunrise_offset", 0 +CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET = "sunrise_offset", timedelta(seconds=0) CONF_SUNRISE_TIME = "sunrise_time" -CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET = "sunset_offset", 0 +CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET = "sunset_offset", timedelta(seconds=0) CONF_SUNSET_TIME = "sunset_time" CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 60 +UNDO_UPDATE_LISTENER = "undo_update_listener" _COMMON_SCHEMA = { vol.Optional(CONF_LIGHTS, default=DEFAULT_LIGHTS): cv.entity_ids, diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index e8de92e2..ad241b0e 100755 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -1,3 +1,4 @@ +# CHECK OUT THE VIZIO COMPONENT! """Adaptive Lighting Component for Home-Assistant.""" import asyncio @@ -5,6 +6,8 @@ import bisect import logging from datetime import timedelta +import voluptuous as vol + import homeassistant.util.dt as dt_util from homeassistant.components.light import ( ATTR_BRIGHTNESS_PCT, @@ -46,6 +49,7 @@ from homeassistant.util.color import ( from .const import ( CONF_DISABLE_BRIGHTNESS_ADJUST, CONF_DISABLE_ENTITY, + UNDO_UPDATE_LISTENER, CONF_DISABLE_STATE, CONF_INITIAL_TRANSITION, CONF_INTERVAL, @@ -92,10 +96,10 @@ _SUPPORT_OPTS = { } _ALLOWED_ORDERS = { - ("solar_sunrise", "solar_noon", "solar_sunset", "solar_midnight"), - ("solar_sunset", "solar_midnight", "solar_sunrise", "solar_noon"), - ("solar_midnight", "solar_sunrise", "solar_noon", "solar_sunset"), - ("solar_noon", "solar_sunset", "solar_midnight", "solar_sunrise"), + (SUN_EVENT_SUNRISE, SUN_EVENT_NOON, SUN_EVENT_SUNSET, SUN_EVENT_MIDNIGHT), + (SUN_EVENT_SUNSET, SUN_EVENT_MIDNIGHT, SUN_EVENT_SUNRISE, SUN_EVENT_NOON), + (SUN_EVENT_MIDNIGHT, SUN_EVENT_SUNRISE, SUN_EVENT_NOON, SUN_EVENT_SUNSET), + (SUN_EVENT_NOON, SUN_EVENT_SUNSET, SUN_EVENT_MIDNIGHT, SUN_EVENT_SUNRISE), } @@ -111,7 +115,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} hass.data[DOMAIN][name] = switch - async_add_entities([switch]) + async_add_entities([switch], update_before_add=True) def _difference_between_states(from_state, to_state): @@ -150,9 +154,7 @@ def _difference_between_states(from_state, to_state): class AdaptiveSwitch(SwitchEntity, RestoreEntity): """Representation of a Adaptive Lighting switch.""" - def __init__( - self, hass, name, config_entry, - ): + def __init__(self, hass, name, config_entry): """Initialize the Adaptive Lighting switch.""" self.hass = hass self._name = name @@ -171,6 +173,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): # Set and unset tracker in async_turn_on and async_turn_off self.unsub_tracker = None + _LOGGER.error(f"Setting up with {self._lights}: config_entry.data: {config_entry.data}, config_entry.options: {config_entry.options}") @property def _lights(self): @@ -431,7 +434,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): # Check whether order is correct events = sorted(events, key=lambda x: x[1]) events_names, _ = zip(*events) - assert events_names in _ALLOWED_ORDERS + assert events_names in _ALLOWED_ORDERS, events_names return events