mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-15 08:14:04 +02:00
Compare commits
1 commit
main
...
do-it-your
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbee9526bd |
2 changed files with 111 additions and 6 deletions
|
|
@ -15,6 +15,20 @@ DOMAIN = "adaptive_lighting"
|
||||||
SUN_EVENT_NOON = "solar_noon"
|
SUN_EVENT_NOON = "solar_noon"
|
||||||
SUN_EVENT_MIDNIGHT = "solar_midnight"
|
SUN_EVENT_MIDNIGHT = "solar_midnight"
|
||||||
|
|
||||||
|
API_EVENT_SEND = "call"
|
||||||
|
API_EVENT_GET = "return"
|
||||||
|
API_RETRIEVE = "retrieve"
|
||||||
|
API_SINGLESHOT_EVENT = "event"
|
||||||
|
API_EVENT_CALC_PERCENT = "on_percent_calc"
|
||||||
|
API_EVENT_ADAPT = "on_adapt_end"
|
||||||
|
API_EVENT_CALC_BRIGHTNESS = "on_brightness_calc"
|
||||||
|
API_EVENT_CALC_COLOR_TEMP = "on_colortemp_calc"
|
||||||
|
API_EVENT_CALC_SUN_EVENTS = "on_sunevents_calc"
|
||||||
|
API_REQUEST_SETTINGS = "settings"
|
||||||
|
API_REQUEST_LAST_ADAPT = "last_service_data"
|
||||||
|
API_REQUEST_LIGHT_INFO = "light_info"
|
||||||
|
API_REQUEST_SWITCH_INFO = "switch_info"
|
||||||
|
|
||||||
DOCS = {CONF_ENTITY_ID: "Entity ID of the switch. 📝"}
|
DOCS = {CONF_ENTITY_ID: "Entity ID of the switch. 📝"}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from datetime import timedelta
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
from time import perf_counter
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
import astral
|
import astral
|
||||||
|
|
@ -99,6 +100,10 @@ import voluptuous as vol
|
||||||
from .const import (
|
from .const import (
|
||||||
ADAPT_BRIGHTNESS_SWITCH,
|
ADAPT_BRIGHTNESS_SWITCH,
|
||||||
ADAPT_COLOR_SWITCH,
|
ADAPT_COLOR_SWITCH,
|
||||||
|
API_EVENT_CALC_BRIGHTNESS,
|
||||||
|
API_EVENT_GET,
|
||||||
|
API_EVENT_SEND,
|
||||||
|
API_SINGLESHOT_EVENT,
|
||||||
ATTR_ADAPT_BRIGHTNESS,
|
ATTR_ADAPT_BRIGHTNESS,
|
||||||
ATTR_ADAPT_COLOR,
|
ATTR_ADAPT_COLOR,
|
||||||
ATTR_TURN_ON_OFF_LISTENER,
|
ATTR_TURN_ON_OFF_LISTENER,
|
||||||
|
|
@ -826,6 +831,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
# Set attributes that can't be modified during runtime
|
# Set attributes that can't be modified during runtime
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.turn_on_off_listener = turn_on_off_listener
|
self.turn_on_off_listener = turn_on_off_listener
|
||||||
|
self.api_caller = ApiEventCall(hass, timeout=2)
|
||||||
|
self.turn_on_off_listener.api_caller = self.api_caller
|
||||||
self.sleep_mode_switch = sleep_mode_switch
|
self.sleep_mode_switch = sleep_mode_switch
|
||||||
self.adapt_color_switch = adapt_color_switch
|
self.adapt_color_switch = adapt_color_switch
|
||||||
self.adapt_brightness_switch = adapt_brightness_switch
|
self.adapt_brightness_switch = adapt_brightness_switch
|
||||||
|
|
@ -1255,6 +1262,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
||||||
filtered_lights = lights
|
filtered_lights = lights
|
||||||
|
|
||||||
if not filtered_lights:
|
if not filtered_lights:
|
||||||
|
self.api_caller.fire_while_idle(locals())
|
||||||
return
|
return
|
||||||
|
|
||||||
await self._update_manual_control_and_maybe_adapt(
|
await self._update_manual_control_and_maybe_adapt(
|
||||||
|
|
@ -1609,6 +1617,7 @@ class SunLightSettings:
|
||||||
return self.sleep_brightness
|
return self.sleep_brightness
|
||||||
if percent > 0:
|
if percent > 0:
|
||||||
return self.max_brightness
|
return self.max_brightness
|
||||||
|
self.api_caller.add_job(API_EVENT_CALC_BRIGHTNESS, locals())
|
||||||
delta_brightness = self.max_brightness - self.min_brightness
|
delta_brightness = self.max_brightness - self.min_brightness
|
||||||
percent = 1 + percent
|
percent = 1 + percent
|
||||||
return (delta_brightness * percent) + self.min_brightness
|
return (delta_brightness * percent) + self.min_brightness
|
||||||
|
|
@ -1618,13 +1627,20 @@ class SunLightSettings:
|
||||||
if percent > 0:
|
if percent > 0:
|
||||||
delta = self.max_color_temp - self.min_color_temp
|
delta = self.max_color_temp - self.min_color_temp
|
||||||
ct = (delta * percent) + self.min_color_temp
|
ct = (delta * percent) + self.min_color_temp
|
||||||
return 5 * round(ct / 5) # round to nearest 5
|
ret = 5 * round(ct / 5) # round to nearest 5
|
||||||
if percent == 0 or not self.adapt_until_sleep:
|
if percent == 0 or not self.adapt_until_sleep:
|
||||||
return self.min_color_temp
|
ret = self.min_color_temp
|
||||||
if self.adapt_until_sleep and percent < 0:
|
if self.adapt_until_sleep and percent < 0:
|
||||||
delta = abs(self.min_color_temp - self.sleep_color_temp)
|
delta = abs(self.min_color_temp - self.sleep_color_temp)
|
||||||
ct = (delta * abs(1 + percent)) + self.sleep_color_temp
|
ct = (delta * abs(1 + percent)) + self.sleep_color_temp
|
||||||
return 5 * round(ct / 5) # round to nearest 5
|
ret = 5 * round(ct / 5) # round to nearest 5
|
||||||
|
ret2 = await asyncio.wait_for(
|
||||||
|
self.api_caller._fire_event_await_return("calc_color_temp_kelvin", locals())
|
||||||
|
)
|
||||||
|
if ret2:
|
||||||
|
_LOGGER.debug("Got %s from apieventcall", ret2)
|
||||||
|
return ret2
|
||||||
|
return ret
|
||||||
|
|
||||||
def get_settings(
|
def get_settings(
|
||||||
self, is_sleep, transition
|
self, is_sleep, transition
|
||||||
|
|
@ -1651,7 +1667,7 @@ class SunLightSettings:
|
||||||
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
|
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
|
||||||
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
|
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
|
||||||
hs_color: tuple[float, float] = color_xy_to_hs(*xy_color)
|
hs_color: tuple[float, float] = color_xy_to_hs(*xy_color)
|
||||||
return {
|
settings = {
|
||||||
"brightness_pct": brightness_pct,
|
"brightness_pct": brightness_pct,
|
||||||
"color_temp_kelvin": color_temp_kelvin,
|
"color_temp_kelvin": color_temp_kelvin,
|
||||||
"color_temp_mired": color_temp_mired,
|
"color_temp_mired": color_temp_mired,
|
||||||
|
|
@ -1660,6 +1676,7 @@ class SunLightSettings:
|
||||||
"hs_color": hs_color,
|
"hs_color": hs_color,
|
||||||
"sun_position": percent,
|
"sun_position": percent,
|
||||||
}
|
}
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
class TurnOnOffListener:
|
class TurnOnOffListener:
|
||||||
|
|
@ -2129,7 +2146,7 @@ class _AsyncSingleShotTimer:
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
"""Run the timer. Don't call this directly, use start() instead."""
|
"""Run the timer. Don't call this directly, use start() instead."""
|
||||||
self.start_time = dt_util.utcnow()
|
self.start_time = perf_counter()
|
||||||
await asyncio.sleep(self.delay)
|
await asyncio.sleep(self.delay)
|
||||||
if self.callback:
|
if self.callback:
|
||||||
if asyncio.iscoroutinefunction(self.callback):
|
if asyncio.iscoroutinefunction(self.callback):
|
||||||
|
|
@ -2156,6 +2173,80 @@ class _AsyncSingleShotTimer:
|
||||||
def remaining_time(self):
|
def remaining_time(self):
|
||||||
"""Return the remaining time before the timer expires."""
|
"""Return the remaining time before the timer expires."""
|
||||||
if self.start_time is not None:
|
if self.start_time is not None:
|
||||||
elapsed_time = (dt_util.utcnow() - self.start_time).total_seconds()
|
elapsed_time = (perf_counter() - self.start_time).total_seconds()
|
||||||
return max(0, self.delay - elapsed_time)
|
return max(0, self.delay - elapsed_time)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEventCall:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
timeout: float,
|
||||||
|
):
|
||||||
|
self.hass = hass
|
||||||
|
self.timeout = timeout
|
||||||
|
self._event_loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
class ApiHandlers:
|
||||||
|
def __init__(self, event_loop):
|
||||||
|
self.event_loop = event_loop
|
||||||
|
self.return_data: dict[str]
|
||||||
|
self.task: asyncio.Event()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def handle_calc_brightness_pct(self, event):
|
||||||
|
self.return_data = event.data
|
||||||
|
self._event_loop.call_soon_threadsafe(self.task.set())
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def handle_calc_color_temp_kelvin(self, event):
|
||||||
|
self.return_data = event.data
|
||||||
|
self._event_loop.call_soon_threadsafe(self.task.set())
|
||||||
|
|
||||||
|
def fire_while_idle(
|
||||||
|
self,
|
||||||
|
light: str,
|
||||||
|
switch: AdaptiveSwitch,
|
||||||
|
turn_on_off_listener: TurnOnOffListener,
|
||||||
|
data: dict[str],
|
||||||
|
event: str,
|
||||||
|
context: Context,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
assert context
|
||||||
|
self.fire_event(event, data)
|
||||||
|
|
||||||
|
async def _lazy_event_firer(self, event, data):
|
||||||
|
self.hass.bus.async_fire(
|
||||||
|
f"{DOMAIN}.{API_SINGLESHOT_EVENT}.{event}",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fire_event(self, event: str, data: dict[str]):
|
||||||
|
self.hass.async_create_task(self._lazy_event_firer(event, data))
|
||||||
|
|
||||||
|
async def _fire_event_await_return(
|
||||||
|
self,
|
||||||
|
event: str,
|
||||||
|
data: dict[str],
|
||||||
|
context: Context,
|
||||||
|
):
|
||||||
|
self.hass.bus.async_fire(
|
||||||
|
f"{DOMAIN}.{API_EVENT_SEND}.{event}",
|
||||||
|
data,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Listen for if the user gives a response.
|
||||||
|
handler = self.ApiHandlers(self)
|
||||||
|
handler_name = f"handle_{event}"
|
||||||
|
handler_func = handler(handler_name)
|
||||||
|
self.hass.bus.listen(f"{DOMAIN}.{API_EVENT_GET}.{event}", handler_func)
|
||||||
|
try:
|
||||||
|
await handler.task.wait()
|
||||||
|
return handler.return_data
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await handler.task.clear()
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.error("Task %s errored with %s", handler_name, handler.task())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue