Initial Home Assistant Configuration
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
""" Implement the Select entities of this implementation """
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from custom_components.home_connect_alt.time import DelayedOperationTime
|
||||
from home_connect_async import Appliance, HomeConnect, HomeConnectError, Events, ConditionalLogger as CL
|
||||
from homeassistant.components.select import SelectEntity
|
||||
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, CONF_DELAYED_OPS_DEFAULT, CONF_TRANSLATION_MODE, CONF_TRANSLATION_MODE_SERVER, DEVICE_ICON_MAP, 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."""
|
||||
#homeconnect:HomeConnect = hass.data[DOMAIN]['homeconnect']
|
||||
entry_conf:Configuration = hass.data[DOMAIN][config_entry.entry_id]
|
||||
homeconnect:HomeConnect = entry_conf["homeconnect"]
|
||||
entity_manager = EntityManager(async_add_entities, "Select")
|
||||
|
||||
def add_appliance(appliance:Appliance) -> None:
|
||||
conf = entry_conf.get_config()
|
||||
|
||||
if appliance.available_programs:
|
||||
device = ProgramSelect(appliance, None, conf)
|
||||
entity_manager.add(device)
|
||||
|
||||
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_DEFAULT or not DelayedOperationTime.has_program_run_time(appliance)):
|
||||
device = DelayedOperationSelect(appliance, option.key, conf, option)
|
||||
# remove the TIME delayed operation entity if it exists
|
||||
reg = async_get(hass)
|
||||
time_entity = reg.async_get_entity_id("time", DOMAIN, device.unique_id)
|
||||
if time_entity:
|
||||
reg.async_remove(time_entity)
|
||||
|
||||
entity_manager.add(device)
|
||||
elif option.allowedvalues and len(option.allowedvalues)>1:
|
||||
device = OptionSelect(appliance, option.key, conf)
|
||||
entity_manager.add(device)
|
||||
|
||||
if appliance.settings:
|
||||
for setting in appliance.settings.values():
|
||||
if (setting.allowedvalues and len(setting.allowedvalues)>1 and not is_boolean_enum(setting.allowedvalues)) \
|
||||
and setting.access != "read" :
|
||||
device = SettingsSelect(appliance, setting.key, conf)
|
||||
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 ProgramSelect(InteractiveEntityBase, SelectEntity):
|
||||
""" Selection of available programs """
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
return f'{self.haId}_programs'
|
||||
|
||||
@property
|
||||
def translation_key(self) -> str:
|
||||
return "programs"
|
||||
|
||||
@property
|
||||
def name_ext(self) -> str:
|
||||
return "Programs"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
if self._appliance.type in DEVICE_ICON_MAP:
|
||||
return DEVICE_ICON_MAP[self._appliance.type]
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
return f"{DOMAIN}__programs"
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return super().available \
|
||||
and self._appliance.available_programs \
|
||||
and (
|
||||
"BSH.Common.Status.RemoteControlActive" not in self._appliance.status or
|
||||
self._appliance.status["BSH.Common.Status.RemoteControlActive"].value
|
||||
)
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return a set of selectable options."""
|
||||
if self._appliance.available_programs:
|
||||
if self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER:
|
||||
return [program.name if program.name else program.key for program in self._appliance.available_programs.values()]
|
||||
return list(self._appliance.available_programs.keys())
|
||||
return []
|
||||
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
current_program = self._appliance.get_applied_program()
|
||||
if current_program:
|
||||
if self._appliance.available_programs and current_program.key in self._appliance.available_programs:
|
||||
# The API sometimes returns programs which are not one of the available programs so we ignore it
|
||||
CL.debug(_LOGGER, CL.LogMode.VERBOSE, "Current selected program is %s", current_program.key)
|
||||
return current_program.name if current_program.name and self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER else current_program.key
|
||||
CL.debug(_LOGGER, CL.LogMode.VERBOSE, "Current program %s is not in available_programs", current_program.key)
|
||||
else:
|
||||
CL.debug(_LOGGER, CL.LogMode.VERBOSE, "Current program is None")
|
||||
return None
|
||||
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
try:
|
||||
if self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER:
|
||||
program = next((p for p in self._appliance.available_programs.values() if p.name == option), None)
|
||||
await self._appliance.async_select_program(program_key=program.key)
|
||||
else:
|
||||
await self._appliance.async_select_program(program_key=option)
|
||||
except HomeConnectError as ex:
|
||||
if ex.error_description:
|
||||
raise HomeAssistantError(f"Failed to set the selected program: {ex.error_description} ({ex.code} - {self._key}={option})")
|
||||
raise HomeAssistantError(f"Failed to set the selected program ({ex.code} - {self._key}={option})")
|
||||
|
||||
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class OptionSelect(InteractiveEntityBase, SelectEntity):
|
||||
""" Selection of program options """
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
return f"{DOMAIN}__options"
|
||||
|
||||
@property
|
||||
def translation_key(self) -> str:
|
||||
return "options"
|
||||
|
||||
@property
|
||||
def name_ext(self) -> str|None:
|
||||
if self._appliance.available_programs:
|
||||
for program in self._appliance.available_programs.values():
|
||||
if program.options and self._key in program.options and program.options[self._key].name:
|
||||
return program.options[self._key].name
|
||||
return None
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return self.get_entity_setting('icon', 'mdi:office-building-cog')
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self.program_option_available
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return a set of selectable options."""
|
||||
# if self.program_option_available:
|
||||
# selected_program_key = self._appliance.selected_program.key
|
||||
# available_program = self._appliance.available_programs.get(selected_program_key)
|
||||
# if available_program:
|
||||
# option = available_program.options.get(self._key)
|
||||
# if option:
|
||||
# #_LOGGER.info("Allowed values for %s : %s", self._key, str(option.allowedvalues))
|
||||
# vals = option.allowedvalues.copy()
|
||||
# vals.append('')
|
||||
# return vals
|
||||
# #_LOGGER.info("Allowed values for %s : %s", self._key, None)
|
||||
option = self._appliance.get_applied_program_available_option(self._key)
|
||||
if option:
|
||||
if self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER:
|
||||
vals = option.allowedvaluesdisplay.copy() if option.allowedvaluesdisplay else option.allowedvalues.copy()
|
||||
else:
|
||||
vals = option.allowedvalues.copy()
|
||||
#vals.append('')
|
||||
return vals
|
||||
|
||||
return []
|
||||
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
# if self._appliance.selected_program.options[self._key].value not in self.options:
|
||||
# _LOGGER.debug("The current option is not in the list of available options")
|
||||
option = self._appliance.get_applied_program_option(self._key)
|
||||
if option:
|
||||
CL.debug(_LOGGER, CL.LogMode.VERBOSE, "Option %s current value: %s", self._key, option.value)
|
||||
if self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER:
|
||||
available_option = self._appliance.get_applied_program_available_option(self._key)
|
||||
if (available_option.allowedvaluesdisplay):
|
||||
idx = available_option.allowedvalues.index(option.value)
|
||||
return available_option.allowedvaluesdisplay[idx]
|
||||
return option.value
|
||||
CL.debug(_LOGGER, CL.LogMode.VERBOSE, "Option %s current value is None", self._key)
|
||||
return None
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
if option == '':
|
||||
_LOGGER.debug('Tried to set an empty option')
|
||||
return
|
||||
try:
|
||||
if self._conf[CONF_TRANSLATION_MODE] == CONF_TRANSLATION_MODE_SERVER:
|
||||
available_option = self._appliance.get_applied_program_available_option(self._key)
|
||||
if (available_option.allowedvaluesdisplay):
|
||||
idx = available_option.allowedvaluesdisplay.index(option)
|
||||
option = available_option.allowedvalues[idx]
|
||||
await self._appliance.async_set_option(self._key, option)
|
||||
except HomeConnectError as ex:
|
||||
if ex.error_description:
|
||||
raise HomeAssistantError(f"Failed to set the selected option: {ex.error_description} ({ex.code})")
|
||||
raise HomeAssistantError(f"Failed to set the selected option: ({ex.code})")
|
||||
|
||||
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class SettingsSelect(InteractiveEntityBase, SelectEntity):
|
||||
""" Selection of settings """
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
return f"{DOMAIN}__settings"
|
||||
|
||||
@property
|
||||
def translation_key(self) -> str:
|
||||
return "settings"
|
||||
|
||||
@property
|
||||
def name_ext(self) -> str|None:
|
||||
if self._key in self._appliance.settings and self._appliance.settings[self._key].name:
|
||||
return self._appliance.settings[self._key].name
|
||||
return None
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return self.get_entity_setting('icon', 'mdi:tune')
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return super().available \
|
||||
and (
|
||||
"BSH.Common.Status.RemoteControlActive" not in self._appliance.status or
|
||||
self._appliance.status["BSH.Common.Status.RemoteControlActive"].value
|
||||
)
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return a set of selectable options."""
|
||||
try:
|
||||
return self._appliance.settings[self._key].allowedvalues
|
||||
except Exception as ex:
|
||||
pass
|
||||
return []
|
||||
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
return self._appliance.settings[self._key].value if self._appliance.settings and self._key in self._appliance.settings else None
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
try:
|
||||
await self._appliance.async_apply_setting(self._key, option)
|
||||
except HomeConnectError as ex:
|
||||
if ex.error_description:
|
||||
raise HomeAssistantError(f"Failed to apply the setting: {ex.error_description} ({ex.code})")
|
||||
raise HomeAssistantError(f"Failed to apply the setting: ({ex.code})")
|
||||
|
||||
|
||||
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class DelayedOperationSelect(InteractiveEntityBase, SelectEntity):
|
||||
""" Class for delayed start select box """
|
||||
def __init__(self, appliance: Appliance, key: str = None, conf: dict = None, hc_obj = None) -> None:
|
||||
super().__init__(appliance, key, conf, hc_obj)
|
||||
self._current = '0:00'
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return self.get_entity_setting('icon', 'mdi:clock-outline')
|
||||
|
||||
@property
|
||||
def name_ext(self) -> str|None:
|
||||
""" Provide the suffix of the name, can be be overriden by sub-classes to provide a custom or translated display name """
|
||||
return self._hc_obj.name if self._hc_obj.name else "Delayed operation"
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
available = super().program_option_available
|
||||
if not available:
|
||||
self._current = '0:00'
|
||||
self._appliance.clear_startonly_option(self._key)
|
||||
return available
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
options = [ "0:00" ]
|
||||
|
||||
if self._appliance.selected_program and self._appliance.selected_program.options and self._key in self._appliance.selected_program.options:
|
||||
selected_program_time = self._appliance.selected_program.options[self._key].value
|
||||
start = 1 if "StartInRelative" in self._key else selected_program_time//1800 + (selected_program_time % 1800 > 0)
|
||||
#end = self._appliance.available_programs[self._appliance.selected_program.key].options[self._key].max
|
||||
end = 49
|
||||
for t in range(start, end):
|
||||
options.append(f"{int(t/2)}:{(t%2)*30:02}")
|
||||
else:
|
||||
self._current = '0:00'
|
||||
self._appliance.clear_startonly_option(self._key)
|
||||
return options
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
return self._current
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
self._current = option
|
||||
if option == '0:00':
|
||||
self._appliance.clear_startonly_option(self._key)
|
||||
return
|
||||
parts = option.split(':')
|
||||
delay = int(parts[0])*3600 + int(parts[1])*60
|
||||
self._appliance.set_startonly_option(self._key, delay)
|
||||
|
||||
async def async_on_update(self, appliance:Appliance, key:str, value) -> None:
|
||||
if key == Events.PROGRAM_FINISHED:
|
||||
self._current = '0:00'
|
||||
self.async_write_ha_state()
|
||||
Reference in New Issue
Block a user