mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-11 22:34:04 +02:00
use async functions and call trackers/listeners in the correct place
This commit is contained in:
parent
b70191d634
commit
4c9d1f6659
3 changed files with 93 additions and 97 deletions
|
|
@ -29,11 +29,10 @@ Technical notes: I had to make a lot of assumptions when writing this app
|
|||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import inspect
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import astral
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.light import ATTR_TRANSITION, VALID_TRANSITION
|
||||
from homeassistant.const import (
|
||||
|
|
@ -44,9 +43,13 @@ from homeassistant.const import (
|
|||
SUN_EVENT_SUNSET,
|
||||
)
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.event import track_sunrise, track_sunset, track_time_change
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_sunrise,
|
||||
async_track_sunset,
|
||||
async_track_time_change,
|
||||
async_track_time_interval,
|
||||
)
|
||||
from homeassistant.util.color import (
|
||||
color_RGB_to_xy,
|
||||
color_temperature_to_rgb,
|
||||
|
|
@ -89,7 +92,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
vol.Optional(CONF_ELEVATION): float,
|
||||
vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int,
|
||||
vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.time_period,
|
||||
vol.Optional(
|
||||
ATTR_TRANSITION, default=DEFAULT_TRANSITION
|
||||
): VALID_TRANSITION,
|
||||
|
|
@ -100,26 +103,6 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
def log(with_return=False, logger=_LOGGER):
|
||||
def _log(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
func_args = inspect.signature(func).bind(*args, **kwargs).arguments
|
||||
key_value_pairs = (
|
||||
f"{k}={v!r}" for k, v in func_args.items() if k != "self"
|
||||
)
|
||||
func_args_str = ", ".join(key_value_pairs)
|
||||
out = f"{func.__qualname__}({func_args_str})"
|
||||
result = func(*args, **kwargs)
|
||||
if with_return:
|
||||
out += f" -> {result}"
|
||||
logger.debug(out)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return _log
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Circadian Lighting component."""
|
||||
conf = config[DOMAIN]
|
||||
|
|
@ -180,23 +163,30 @@ class CircadianLighting:
|
|||
self._xy_color = self.calc_xy()
|
||||
self._hs_color = self.calc_hs()
|
||||
|
||||
self.update = Throttle(timedelta(seconds=interval))(self._update)
|
||||
if self._manual_time["sunrise"] is not None:
|
||||
async_track_time_change(
|
||||
self.hass,
|
||||
self.update,
|
||||
hour=self._manual_time["sunrise"].hour,
|
||||
minute=self._manual_time["sunrise"].minute,
|
||||
second=self._manual_time["sunrise"].second,
|
||||
)
|
||||
else:
|
||||
async_track_sunrise(self.hass, self.update, self._sunrise_offset)
|
||||
|
||||
for which in ["sunrise", "sunrise"]:
|
||||
time = self._manual_time[which]
|
||||
if time is not None:
|
||||
track_time_change(
|
||||
self.hass,
|
||||
self._update,
|
||||
hour=time.hour,
|
||||
minute=time.minute,
|
||||
second=time.second,
|
||||
)
|
||||
if self._manual_time["sunset"] is not None:
|
||||
async_track_time_change(
|
||||
self.hass,
|
||||
self.update,
|
||||
hour=self._manual_time["sunset"].hour,
|
||||
minute=self._manual_time["sunset"].minute,
|
||||
second=self._manual_time["sunset"].second,
|
||||
)
|
||||
else:
|
||||
async_track_sunset(self.hass, self.update, self._sunset_offset)
|
||||
|
||||
track_sunrise(self.hass, self._update, self._sunrise_offset)
|
||||
track_sunset(self.hass, self._update, self._sunset_offset)
|
||||
async_track_time_interval(self.hass, self.update, interval)
|
||||
|
||||
@log(with_return=True)
|
||||
def get_timezone(self):
|
||||
tf = TimezoneFinder()
|
||||
timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude)
|
||||
|
|
@ -263,8 +253,8 @@ class CircadianLighting:
|
|||
|
||||
today = self.get_sunrise_sunset(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
|
||||
# It's before sunrise (after midnight), because it's before
|
||||
# sunrise (and after midnight) sunset must have happend yesterday.
|
||||
yesterday = self.get_sunrise_sunset(now - timedelta(days=1))
|
||||
today[SUN_EVENT_SUNSET] = yesterday[SUN_EVENT_SUNSET]
|
||||
if (
|
||||
|
|
@ -274,8 +264,8 @@ class CircadianLighting:
|
|||
# Solar midnight is after sunset so use yesterdays's time
|
||||
today[SUN_EVENT_MIDNIGHT] = yesterday[SUN_EVENT_MIDNIGHT]
|
||||
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
|
||||
# It's after sunset (before midnight), because it's after sunset
|
||||
# (and before midnight) sunrise should happen tomorrow.
|
||||
tomorrow = self.get_sunrise_sunset(now + timedelta(days=1))
|
||||
today[SUN_EVENT_SUNRISE] = tomorrow[SUN_EVENT_SUNRISE]
|
||||
if (
|
||||
|
|
@ -335,12 +325,11 @@ class CircadianLighting:
|
|||
def calc_hs(self):
|
||||
return color_xy_to_hs(*self.calc_xy())
|
||||
|
||||
@log()
|
||||
def _update(self):
|
||||
async def update(self, _=None):
|
||||
"""Update Circadian Values."""
|
||||
self._percent = self.calc_percent()
|
||||
self._colortemp = self.calc_colortemp()
|
||||
self._rgb_color = self.calc_rgb()
|
||||
self._xy_color = self.calc_xy()
|
||||
self._hs_color = self.calc_hs()
|
||||
dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC)
|
||||
async_dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
Circadian Lighting Sensor for Home-Assistant.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from custom_components.circadian_lighting import DOMAIN
|
||||
from . import CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN
|
||||
|
||||
ICON = "mdi:theme-light-dark"
|
||||
|
||||
|
|
@ -16,11 +16,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
circadian_lighting = hass.data.get(DOMAIN)
|
||||
if circadian_lighting is not None:
|
||||
sensor = CircadianSensor(hass, circadian_lighting)
|
||||
add_devices([sensor])
|
||||
add_devices([sensor], True)
|
||||
|
||||
def update(call=None):
|
||||
"""Update component."""
|
||||
circadian_lighting._update()
|
||||
circadian_lighting.update()
|
||||
|
||||
service_name = "values_update"
|
||||
hass.services.register(DOMAIN, service_name, update)
|
||||
|
|
@ -78,9 +78,20 @@ class CircadianSensor(Entity):
|
|||
"xy_color": self._circadian_lighting._xy_color,
|
||||
}
|
||||
|
||||
def update(self):
|
||||
"""Fetch new state data for the sensor.
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
This is the only method that should fetch new data for Home Assistant.
|
||||
"""
|
||||
self._circadian_lighting.update()
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Connect dispatcher to signal from CircadianLighting object."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_callback
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_callback(self) -> None:
|
||||
"""Triggers update of properties after receiving signal from CircadianLighting."""
|
||||
self.async_schedule_update_ha_state(force_refresh=False)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
Circadian Lighting Switch for Home-Assistant.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
|
|
@ -16,6 +18,7 @@ from homeassistant.components.light import (
|
|||
)
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.light import VALID_TRANSITION, is_on
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
|
|
@ -23,8 +26,8 @@ from homeassistant.const import (
|
|||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_connect
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.color import (
|
||||
|
|
@ -34,17 +37,7 @@ from homeassistant.util.color import (
|
|||
color_xy_to_hs,
|
||||
)
|
||||
|
||||
from custom_components.circadian_lighting import (
|
||||
CIRCADIAN_LIGHTING_UPDATE_TOPIC,
|
||||
DOMAIN,
|
||||
log,
|
||||
)
|
||||
|
||||
try:
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
except ImportError:
|
||||
from homeassistant.components.switch import SwitchDevice as SwitchEntity
|
||||
|
||||
from . import CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -186,14 +179,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
self._lights_types[light] = "brightness"
|
||||
self._lights = list(self._lights_types.keys())
|
||||
|
||||
# Register callbacks
|
||||
dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch)
|
||||
track_state_change(hass, self._lights, self.light_state_changed)
|
||||
if self._sleep_entity is not None:
|
||||
track_state_change(hass, self._sleep_entity, self.sleep_state_changed)
|
||||
if self._disable_entity is not None:
|
||||
track_state_change(hass, self._disable_entity, self.disable_state_changed)
|
||||
|
||||
@property
|
||||
def entity_id(self):
|
||||
"""Return the entity ID of the switch."""
|
||||
|
|
@ -211,9 +196,33 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity about to be added to hass."""
|
||||
# If not None, we got an initial value.
|
||||
await super().async_added_to_hass()
|
||||
if self._state is not None:
|
||||
# Add callback
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch
|
||||
)
|
||||
)
|
||||
|
||||
# Add listeners
|
||||
async_track_state_change(self.hass, self._lights, self.light_state_changed)
|
||||
|
||||
if self._sleep_entity is not None:
|
||||
async_track_state_change(
|
||||
self.hass, self._sleep_entity, self.sleep_state_changed
|
||||
)
|
||||
|
||||
if self._disable_entity is not None:
|
||||
disable_state_changed = functools.partial(
|
||||
self._update_switch, transition=self._initial_transition, force=True
|
||||
)
|
||||
async_track_state_change(
|
||||
self.hass,
|
||||
self._disable_entity,
|
||||
disable_state_changed,
|
||||
from_state=self._disable_state,
|
||||
)
|
||||
|
||||
if self._state is not None: # If not None, we got an initial value
|
||||
return
|
||||
|
||||
state = await self.async_get_last_state()
|
||||
|
|
@ -237,16 +246,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
"""Turn on circadian lighting."""
|
||||
self._state = True
|
||||
self._update_switch(transition=self._initial_transition, force=True)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn off circadian lighting."""
|
||||
self._state = False
|
||||
self.schedule_update_ha_state()
|
||||
self._hs_color = None
|
||||
self._brightness = None
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def is_sleep(self):
|
||||
return (
|
||||
self._sleep_entity is not None
|
||||
|
|
@ -283,7 +289,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
percent = (100 + self._circadian_lighting._percent) / 100
|
||||
return (delta_brightness * percent) + self._min_brightness
|
||||
|
||||
@log(logger=_LOGGER)
|
||||
def _update_switch(self, lights=None, transition=None, force=False):
|
||||
if self._only_once and not force:
|
||||
return
|
||||
|
|
@ -291,14 +296,12 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
self._brightness = self.calc_brightness()
|
||||
self._adjust_lights(lights or self._lights, transition)
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def _is_disabled(self):
|
||||
return (
|
||||
self._disable_entity is not None
|
||||
and self.hass.states.get(self._disable_entity).state in self._disable_state
|
||||
)
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def _should_adjust(self):
|
||||
if self._state is not True:
|
||||
return False
|
||||
|
|
@ -306,7 +309,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
return False
|
||||
return True
|
||||
|
||||
def _adjust_lights(self, lights, transition=None):
|
||||
def _adjust_lights(self, lights, transition):
|
||||
if not self._should_adjust():
|
||||
return
|
||||
|
||||
|
|
@ -337,17 +340,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity):
|
|||
self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data)
|
||||
_LOGGER.debug(f"{light} {light_type} Adjusted - {service_data}")
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def light_state_changed(self, entity_id, from_state, to_state):
|
||||
if to_state.state == "on" and from_state.state != "on":
|
||||
self._update_switch([entity_id], self._initial_transition, force=True)
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def sleep_state_changed(self, entity_id, from_state, to_state):
|
||||
if to_state.state in self._sleep_state or from_state.state in self._sleep_state:
|
||||
self._update_switch(transition=self._initial_transition, force=True)
|
||||
|
||||
@log(with_return=True, logger=_LOGGER)
|
||||
def disable_state_changed(self, entity_id, from_state, to_state):
|
||||
if from_state.state in self._disable_state:
|
||||
self._update_switch(transition=self._initial_transition, force=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue