Initial Home Assistant Configuration
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import datetime
|
||||
from homeassistant.components.time import TimeEntity, time, timedelta
|
||||
from home_connect_async import Appliance, HomeConnect, HomeConnectError, Events, ConditionalLogger as CL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.entity_registry import async_get
|
||||
|
||||
from .common import InteractiveEntityBase, EntityManager, is_boolean_enum, Configuration
|
||||
from .const import CONF_DELAYED_OPS, CONF_DELAYED_OPS_ABSOLUTE_TIME, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_setup_entry(hass:HomeAssistant , config_entry:ConfigType, async_add_entities:AddEntitiesCallback) -> None:
|
||||
"""Add Selects for passed config_entry in HA."""
|
||||
entry_conf:Configuration = hass.data[DOMAIN][config_entry.entry_id]
|
||||
homeconnect:HomeConnect = entry_conf["homeconnect"]
|
||||
entity_manager = EntityManager(async_add_entities, "Time")
|
||||
|
||||
|
||||
def add_appliance(appliance:Appliance) -> None:
|
||||
conf = entry_conf.get_config()
|
||||
|
||||
if appliance.available_programs:
|
||||
for program in appliance.available_programs.values():
|
||||
if program.options:
|
||||
for option in program.options.values():
|
||||
if conf.get_entity_setting(option.key, "type") == "DelayedOperation" \
|
||||
and entry_conf[CONF_DELAYED_OPS]==CONF_DELAYED_OPS_ABSOLUTE_TIME \
|
||||
and DelayedOperationTime.has_program_run_time(appliance):
|
||||
device = DelayedOperationTime(appliance, option.key, conf, option)
|
||||
# remove the SELECT delayed operation entity if it exists
|
||||
reg = async_get(hass)
|
||||
select_entity = reg.async_get_entity_id("select", DOMAIN, device.unique_id)
|
||||
if select_entity:
|
||||
reg.async_remove(select_entity)
|
||||
entity_manager.add(device)
|
||||
|
||||
entity_manager.register()
|
||||
|
||||
def remove_appliance(appliance:Appliance) -> None:
|
||||
entity_manager.remove_appliance(appliance)
|
||||
|
||||
homeconnect.register_callback(add_appliance, [Events.PAIRED, Events.DATA_CHANGED, Events.PROGRAM_STARTED, Events.PROGRAM_SELECTED])
|
||||
homeconnect.register_callback(remove_appliance, Events.DEPAIRED)
|
||||
for appliance in homeconnect.appliances.values():
|
||||
add_appliance(appliance)
|
||||
|
||||
|
||||
class DelayedOperationTime(InteractiveEntityBase, TimeEntity):
|
||||
""" Class for setting delayed start by the program end time """
|
||||
should_poll = True
|
||||
|
||||
def __init__(self, appliance: Appliance, key: str = None, conf: dict = None, hc_obj = None) -> None:
|
||||
super().__init__(appliance, key, conf, hc_obj)
|
||||
self._current:time = None
|
||||
@property
|
||||
def name_ext(self) -> str|None:
|
||||
return self._hc_obj.name if self._hc_obj.name else "Delayed operation"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return self.get_entity_setting('icon', 'mdi:clock-outline')
|
||||
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
||||
# We must have the program run time for this entity to work
|
||||
available = super().program_option_available and self.get_program_run_time(self._appliance) is not None
|
||||
|
||||
if not available:
|
||||
self._appliance.clear_startonly_option(self._key)
|
||||
return available
|
||||
|
||||
async def async_set_value(self, value: time) -> None:
|
||||
"""Update the current value."""
|
||||
self._current = self.adjust_time(value, True)
|
||||
|
||||
#self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def native_value(self) -> time:
|
||||
"""Return the entity value to represent the entity state."""
|
||||
if self._current is None:
|
||||
self._current = self.init_time()
|
||||
|
||||
if self._appliance.startonly_options and self._key in self._appliance.startonly_options:
|
||||
self._current = self.adjust_time(self._current, True)
|
||||
else:
|
||||
self._current = self.adjust_time(self._current, False)
|
||||
return self._current
|
||||
|
||||
|
||||
def adjust_time(self, t:time, set_option:bool) -> time|None:
|
||||
""" Adjust the time state when required """
|
||||
|
||||
now = datetime.datetime.now()
|
||||
endtime = datetime.datetime(year=now.year, month=now.month, day=now.day, hour=t.hour, minute=t.minute)
|
||||
|
||||
if (now.hour > endtime.hour) or (now.hour == endtime.hour and now.minute > endtime.minute):
|
||||
# if the specified time is smaller than now then it means tomorrow
|
||||
endtime += datetime.timedelta(days=1)
|
||||
|
||||
program_run_time = self.get_program_run_time(self._appliance)
|
||||
|
||||
if not program_run_time:
|
||||
return None
|
||||
|
||||
if endtime < now + timedelta(seconds=program_run_time):
|
||||
# the set end time is closer then the program run time so change it to the expected end of the program
|
||||
# and cancel the set delay option
|
||||
endtime = now + timedelta(seconds=program_run_time)
|
||||
#self._current = time(hour=endtime.hour, minute=endtime.minute)
|
||||
if self._appliance.startonly_options and self._key in self._appliance.startonly_options:
|
||||
_LOGGER.debug("Clearing startonly option %s", self._key)
|
||||
self._appliance.clear_startonly_option(self._key)
|
||||
elif set_option:
|
||||
delay = (endtime-now).total_seconds()
|
||||
if "StartInRelative" in self._key:
|
||||
delay -= program_run_time
|
||||
|
||||
# round the delay to the stepsize
|
||||
stepsize_option = self._appliance.get_applied_program_available_option(self._key)
|
||||
stepsize = stepsize_option.stepsize if stepsize_option and stepsize_option.stepsize and stepsize_option.stepsize != 0 else 60
|
||||
delay = int(delay/stepsize)*stepsize
|
||||
|
||||
_LOGGER.debug("Setting startonly option %s to: %i", self._key, delay)
|
||||
self._appliance.set_startonly_option(self._key, delay)
|
||||
|
||||
return time(hour=endtime.hour, minute=endtime.minute)
|
||||
|
||||
def init_time(self) -> time:
|
||||
""" Initialize the time state """
|
||||
inittime = datetime.datetime.now() + timedelta(minutes=1)
|
||||
t = time(hour=inittime.hour, minute=inittime.minute)
|
||||
return self.adjust_time(t, False)
|
||||
|
||||
@classmethod
|
||||
def get_program_run_time(cls, appliance:Appliance) -> int|None:
|
||||
""" Try to get the expected run time of the selected program or the remaining time of the running program """
|
||||
time_option_keys = [
|
||||
"BSH.Common.Option.RemainingProgramTime",
|
||||
"BSH.Common.Option.FinishInRelative",
|
||||
"BSH.Common.Option.EstimatedTotalProgramTime",
|
||||
]
|
||||
|
||||
for key in time_option_keys:
|
||||
o = appliance.get_applied_program_option(key)
|
||||
if o:
|
||||
return o.value
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def has_program_run_time(cls, appliance:Appliance) ->bool:
|
||||
""" Check if it's possible to get a program run time estimate """
|
||||
return cls.get_program_run_time(appliance) is not None
|
||||
|
||||
|
||||
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
|
||||
# reset the end time clock when a different program is selected
|
||||
if key == Events.PROGRAM_SELECTED or "RemoteControlStartAllowed" in key:
|
||||
self._current = self.init_time()
|
||||
self.async_write_ha_state()
|
||||
Reference in New Issue
Block a user