mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-12 14:54:04 +02:00
WIP
This commit is contained in:
parent
a888afd6dc
commit
bbee9526bd
2 changed files with 111 additions and 6 deletions
|
|
@ -15,6 +15,20 @@ DOMAIN = "adaptive_lighting"
|
|||
SUN_EVENT_NOON = "solar_noon"
|
||||
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. 📝"}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from datetime import timedelta
|
|||
import functools
|
||||
import logging
|
||||
import math
|
||||
from time import perf_counter
|
||||
from typing import Any, Literal
|
||||
|
||||
import astral
|
||||
|
|
@ -99,6 +100,10 @@ import voluptuous as vol
|
|||
from .const import (
|
||||
ADAPT_BRIGHTNESS_SWITCH,
|
||||
ADAPT_COLOR_SWITCH,
|
||||
API_EVENT_CALC_BRIGHTNESS,
|
||||
API_EVENT_GET,
|
||||
API_EVENT_SEND,
|
||||
API_SINGLESHOT_EVENT,
|
||||
ATTR_ADAPT_BRIGHTNESS,
|
||||
ATTR_ADAPT_COLOR,
|
||||
ATTR_TURN_ON_OFF_LISTENER,
|
||||
|
|
@ -826,6 +831,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
# Set attributes that can't be modified during runtime
|
||||
self.hass = hass
|
||||
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.adapt_color_switch = adapt_color_switch
|
||||
self.adapt_brightness_switch = adapt_brightness_switch
|
||||
|
|
@ -1255,6 +1262,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
filtered_lights = lights
|
||||
|
||||
if not filtered_lights:
|
||||
self.api_caller.fire_while_idle(locals())
|
||||
return
|
||||
|
||||
await self._update_manual_control_and_maybe_adapt(
|
||||
|
|
@ -1609,6 +1617,7 @@ class SunLightSettings:
|
|||
return self.sleep_brightness
|
||||
if percent > 0:
|
||||
return self.max_brightness
|
||||
self.api_caller.add_job(API_EVENT_CALC_BRIGHTNESS, locals())
|
||||
delta_brightness = self.max_brightness - self.min_brightness
|
||||
percent = 1 + percent
|
||||
return (delta_brightness * percent) + self.min_brightness
|
||||
|
|
@ -1618,13 +1627,20 @@ class SunLightSettings:
|
|||
if percent > 0:
|
||||
delta = self.max_color_temp - 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:
|
||||
return self.min_color_temp
|
||||
ret = self.min_color_temp
|
||||
if self.adapt_until_sleep and percent < 0:
|
||||
delta = abs(self.min_color_temp - 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(
|
||||
self, is_sleep, transition
|
||||
|
|
@ -1651,7 +1667,7 @@ class SunLightSettings:
|
|||
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
|
||||
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
|
||||
hs_color: tuple[float, float] = color_xy_to_hs(*xy_color)
|
||||
return {
|
||||
settings = {
|
||||
"brightness_pct": brightness_pct,
|
||||
"color_temp_kelvin": color_temp_kelvin,
|
||||
"color_temp_mired": color_temp_mired,
|
||||
|
|
@ -1660,6 +1676,7 @@ class SunLightSettings:
|
|||
"hs_color": hs_color,
|
||||
"sun_position": percent,
|
||||
}
|
||||
return settings
|
||||
|
||||
|
||||
class TurnOnOffListener:
|
||||
|
|
@ -2129,7 +2146,7 @@ class _AsyncSingleShotTimer:
|
|||
|
||||
async def _run(self):
|
||||
"""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)
|
||||
if self.callback:
|
||||
if asyncio.iscoroutinefunction(self.callback):
|
||||
|
|
@ -2156,6 +2173,80 @@ class _AsyncSingleShotTimer:
|
|||
def remaining_time(self):
|
||||
"""Return the remaining time before the timer expires."""
|
||||
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 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