mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add entities that represent program options to Home Connect (#138674)
* Add program options as entities * Use program options constraints * Only fetch the available options on refresh * Extract the option definitions getter from the loop * Add the option entities only when it is required * Fix typo
This commit is contained in:
parent
a0c2781355
commit
92788a04ff
@ -1,5 +1,6 @@
|
||||
"""Common callbacks for all Home Connect platforms."""
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import cast
|
||||
@ -9,7 +10,32 @@ from aiohomeconnect.model import EventKey
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
||||
|
||||
|
||||
def _create_option_entities(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
known_entity_unique_ids: dict[str, str],
|
||||
get_option_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData],
|
||||
list[HomeConnectOptionEntity],
|
||||
],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Create the required option entities for the appliances."""
|
||||
option_entities_to_add = [
|
||||
entity
|
||||
for entity in get_option_entities_for_appliance(entry, appliance)
|
||||
if entity.unique_id not in known_entity_unique_ids
|
||||
]
|
||||
known_entity_unique_ids.update(
|
||||
{
|
||||
cast(str, entity.unique_id): appliance.info.ha_id
|
||||
for entity in option_entities_to_add
|
||||
}
|
||||
)
|
||||
async_add_entities(option_entities_to_add)
|
||||
|
||||
|
||||
def _handle_paired_or_connected_appliance(
|
||||
@ -18,6 +44,12 @@ def _handle_paired_or_connected_appliance(
|
||||
get_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData], list[HomeConnectEntity]
|
||||
],
|
||||
get_option_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData],
|
||||
list[HomeConnectOptionEntity],
|
||||
]
|
||||
| None,
|
||||
changed_options_listener_remove_callbacks: dict[str, list[Callable[[], None]]],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Handle a new paired appliance or an appliance that has been connected.
|
||||
@ -34,6 +66,28 @@ def _handle_paired_or_connected_appliance(
|
||||
for entity in get_entities_for_appliance(entry, appliance)
|
||||
if entity.unique_id not in known_entity_unique_ids
|
||||
]
|
||||
if get_option_entities_for_appliance:
|
||||
entities_to_add.extend(
|
||||
entity
|
||||
for entity in get_option_entities_for_appliance(entry, appliance)
|
||||
if entity.unique_id not in known_entity_unique_ids
|
||||
)
|
||||
changed_options_listener_remove_callback = (
|
||||
entry.runtime_data.async_add_listener(
|
||||
partial(
|
||||
_create_option_entities,
|
||||
entry,
|
||||
appliance,
|
||||
known_entity_unique_ids,
|
||||
get_option_entities_for_appliance,
|
||||
async_add_entities,
|
||||
),
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(changed_options_listener_remove_callback)
|
||||
changed_options_listener_remove_callbacks[appliance.info.ha_id].append(
|
||||
changed_options_listener_remove_callback
|
||||
)
|
||||
known_entity_unique_ids.update(
|
||||
{
|
||||
cast(str, entity.unique_id): appliance.info.ha_id
|
||||
@ -47,11 +101,17 @@ def _handle_paired_or_connected_appliance(
|
||||
def _handle_depaired_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
known_entity_unique_ids: dict[str, str],
|
||||
changed_options_listener_remove_callbacks: dict[str, list[Callable[[], None]]],
|
||||
) -> None:
|
||||
"""Handle a removed appliance."""
|
||||
for entity_unique_id, appliance_id in known_entity_unique_ids.copy().items():
|
||||
if appliance_id not in entry.runtime_data.data:
|
||||
known_entity_unique_ids.pop(entity_unique_id, None)
|
||||
if appliance_id in changed_options_listener_remove_callbacks:
|
||||
for listener in changed_options_listener_remove_callbacks.pop(
|
||||
appliance_id
|
||||
):
|
||||
listener()
|
||||
|
||||
|
||||
def setup_home_connect_entry(
|
||||
@ -60,13 +120,44 @@ def setup_home_connect_entry(
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData], list[HomeConnectEntity]
|
||||
],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
get_option_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData],
|
||||
list[HomeConnectOptionEntity],
|
||||
]
|
||||
| None = None,
|
||||
) -> None:
|
||||
"""Set up the callbacks for paired and depaired appliances."""
|
||||
known_entity_unique_ids: dict[str, str] = {}
|
||||
changed_options_listener_remove_callbacks: dict[str, list[Callable[[], None]]] = (
|
||||
defaultdict(list)
|
||||
)
|
||||
|
||||
entities: list[HomeConnectEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities_to_add = get_entities_for_appliance(entry, appliance)
|
||||
if get_option_entities_for_appliance:
|
||||
entities_to_add.extend(get_option_entities_for_appliance(entry, appliance))
|
||||
for event_key in (
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
):
|
||||
changed_options_listener_remove_callback = (
|
||||
entry.runtime_data.async_add_listener(
|
||||
partial(
|
||||
_create_option_entities,
|
||||
entry,
|
||||
appliance,
|
||||
known_entity_unique_ids,
|
||||
get_option_entities_for_appliance,
|
||||
async_add_entities,
|
||||
),
|
||||
(appliance.info.ha_id, event_key),
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(changed_options_listener_remove_callback)
|
||||
changed_options_listener_remove_callbacks[appliance.info.ha_id].append(
|
||||
changed_options_listener_remove_callback
|
||||
)
|
||||
known_entity_unique_ids.update(
|
||||
{
|
||||
cast(str, entity.unique_id): appliance.info.ha_id
|
||||
@ -83,6 +174,8 @@ def setup_home_connect_entry(
|
||||
entry,
|
||||
known_entity_unique_ids,
|
||||
get_entities_for_appliance,
|
||||
get_option_entities_for_appliance,
|
||||
changed_options_listener_remove_callbacks,
|
||||
async_add_entities,
|
||||
),
|
||||
(
|
||||
@ -93,7 +186,12 @@ def setup_home_connect_entry(
|
||||
)
|
||||
entry.async_on_unload(
|
||||
entry.runtime_data.async_add_special_listener(
|
||||
partial(_handle_depaired_appliance, entry, known_entity_unique_ids),
|
||||
partial(
|
||||
_handle_depaired_appliance,
|
||||
entry,
|
||||
known_entity_unique_ids,
|
||||
changed_options_listener_remove_callbacks,
|
||||
),
|
||||
(EventKey.BSH_COMMON_APPLIANCE_DEPAIRED,),
|
||||
)
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from aiohomeconnect.client import Client as HomeConnectClient
|
||||
from aiohomeconnect.model import (
|
||||
@ -17,6 +17,8 @@ from aiohomeconnect.model import (
|
||||
EventType,
|
||||
GetSetting,
|
||||
HomeAppliance,
|
||||
OptionKey,
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
Status,
|
||||
StatusKey,
|
||||
@ -28,7 +30,7 @@ from aiohomeconnect.model.error import (
|
||||
HomeConnectRequestError,
|
||||
UnauthorizedError,
|
||||
)
|
||||
from aiohomeconnect.model.program import EnumerateProgram
|
||||
from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption
|
||||
from propcache.api import cached_property
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -53,6 +55,7 @@ class HomeConnectApplianceData:
|
||||
|
||||
events: dict[EventKey, Event]
|
||||
info: HomeAppliance
|
||||
options: dict[OptionKey, ProgramDefinitionOption]
|
||||
programs: list[EnumerateProgram]
|
||||
settings: dict[SettingKey, GetSetting]
|
||||
status: dict[StatusKey, Status]
|
||||
@ -61,6 +64,8 @@ class HomeConnectApplianceData:
|
||||
"""Update data with data from other instance."""
|
||||
self.events.update(other.events)
|
||||
self.info.connected = other.info.connected
|
||||
self.options.clear()
|
||||
self.options.update(other.options)
|
||||
self.programs.clear()
|
||||
self.programs.extend(other.programs)
|
||||
self.settings.update(other.settings)
|
||||
@ -172,8 +177,9 @@ class HomeConnectCoordinator(
|
||||
settings = self.data[event_message_ha_id].settings
|
||||
events = self.data[event_message_ha_id].events
|
||||
for event in event_message.data.items:
|
||||
if event.key in SettingKey:
|
||||
setting_key = SettingKey(event.key)
|
||||
event_key = event.key
|
||||
if event_key in SettingKey:
|
||||
setting_key = SettingKey(event_key)
|
||||
if setting_key in settings:
|
||||
settings[setting_key].value = event.value
|
||||
else:
|
||||
@ -183,7 +189,16 @@ class HomeConnectCoordinator(
|
||||
value=event.value,
|
||||
)
|
||||
else:
|
||||
events[event.key] = event
|
||||
if event_key in (
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
):
|
||||
await self.update_options(
|
||||
event_message_ha_id,
|
||||
event_key,
|
||||
ProgramKey(cast(str, event.value)),
|
||||
)
|
||||
events[event_key] = event
|
||||
self._call_event_listener(event_message)
|
||||
|
||||
case EventType.EVENT:
|
||||
@ -338,6 +353,7 @@ class HomeConnectCoordinator(
|
||||
|
||||
programs = []
|
||||
events = {}
|
||||
options = {}
|
||||
if appliance.type in APPLIANCES_WITH_PROGRAMS:
|
||||
try:
|
||||
all_programs = await self.client.get_all_programs(appliance.ha_id)
|
||||
@ -351,15 +367,17 @@ class HomeConnectCoordinator(
|
||||
)
|
||||
else:
|
||||
programs.extend(all_programs.programs)
|
||||
current_program_key = None
|
||||
program_options = None
|
||||
for program, event_key in (
|
||||
(
|
||||
all_programs.active,
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
),
|
||||
(
|
||||
all_programs.selected,
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
),
|
||||
(
|
||||
all_programs.active,
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
),
|
||||
):
|
||||
if program and program.key:
|
||||
events[event_key] = Event(
|
||||
@ -370,10 +388,30 @@ class HomeConnectCoordinator(
|
||||
"",
|
||||
program.key,
|
||||
)
|
||||
current_program_key = program.key
|
||||
program_options = program.options
|
||||
if current_program_key:
|
||||
options = await self.get_options_definitions(
|
||||
appliance.ha_id, current_program_key
|
||||
)
|
||||
for option in program_options or []:
|
||||
option_event_key = EventKey(option.key)
|
||||
events[option_event_key] = Event(
|
||||
option_event_key,
|
||||
option.key,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
option.value,
|
||||
option.name,
|
||||
display_value=option.display_value,
|
||||
unit=option.unit,
|
||||
)
|
||||
|
||||
appliance_data = HomeConnectApplianceData(
|
||||
events=events,
|
||||
info=appliance,
|
||||
options=options,
|
||||
programs=programs,
|
||||
settings=settings,
|
||||
status=status,
|
||||
@ -383,3 +421,48 @@ class HomeConnectCoordinator(
|
||||
appliance_data = appliance_data_to_update
|
||||
|
||||
return appliance_data
|
||||
|
||||
async def get_options_definitions(
|
||||
self, ha_id: str, program_key: ProgramKey
|
||||
) -> dict[OptionKey, ProgramDefinitionOption]:
|
||||
"""Get options with constraints for appliance."""
|
||||
return {
|
||||
option.key: option
|
||||
for option in (
|
||||
await self.client.get_available_program(ha_id, program_key=program_key)
|
||||
).options
|
||||
or []
|
||||
}
|
||||
|
||||
async def update_options(
|
||||
self, ha_id: str, event_key: EventKey, program_key: ProgramKey
|
||||
) -> None:
|
||||
"""Update options for appliance."""
|
||||
options = self.data[ha_id].options
|
||||
events = self.data[ha_id].events
|
||||
options_to_notify = options.copy()
|
||||
options.clear()
|
||||
if program_key is not ProgramKey.UNKNOWN:
|
||||
options.update(await self.get_options_definitions(ha_id, program_key))
|
||||
|
||||
for option in options.values():
|
||||
option_value = option.constraints.default if option.constraints else None
|
||||
if option_value is not None:
|
||||
option_event_key = EventKey(option.key)
|
||||
events[option_event_key] = Event(
|
||||
option_event_key,
|
||||
option.key.value,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
option_value,
|
||||
option.name,
|
||||
unit=option.unit,
|
||||
)
|
||||
options_to_notify.update(options)
|
||||
for option_key in options_to_notify:
|
||||
for listener in self.context_listeners.get(
|
||||
(ha_id, EventKey(option_key)),
|
||||
[],
|
||||
):
|
||||
listener()
|
||||
|
@ -1,17 +1,22 @@
|
||||
"""Home Connect entity base class."""
|
||||
|
||||
from abc import abstractmethod
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from aiohomeconnect.model import EventKey
|
||||
from aiohomeconnect.model import EventKey, OptionKey
|
||||
from aiohomeconnect.model.error import ActiveProgramNotSetError, HomeConnectError
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectCoordinator
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -60,3 +65,59 @@ class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
|
||||
return (
|
||||
self.appliance.info.connected and self._attr_available and super().available
|
||||
)
|
||||
|
||||
|
||||
class HomeConnectOptionEntity(HomeConnectEntity):
|
||||
"""Class for entities that represents program options."""
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return super().available and self.bsh_key in self.appliance.options
|
||||
|
||||
@property
|
||||
def option_value(self) -> str | int | float | bool | None:
|
||||
"""Return the state of the entity."""
|
||||
if event := self.appliance.events.get(EventKey(self.bsh_key)):
|
||||
return event.value
|
||||
return None
|
||||
|
||||
async def async_set_option(self, value: str | float | bool) -> None:
|
||||
"""Set an option for the entity."""
|
||||
try:
|
||||
# We try to set the active program option first,
|
||||
# if it fails we try to set the selected program option
|
||||
with contextlib.suppress(ActiveProgramNotSetError):
|
||||
await self.coordinator.client.set_active_program_option(
|
||||
self.appliance.info.ha_id,
|
||||
option_key=self.bsh_key,
|
||||
value=value,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Updated %s for the active program, new state: %s",
|
||||
self.entity_id,
|
||||
self.state,
|
||||
)
|
||||
return
|
||||
|
||||
await self.coordinator.client.set_selected_program_option(
|
||||
self.appliance.info.ha_id,
|
||||
option_key=self.bsh_key,
|
||||
value=value,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Updated %s for the selected program, new state: %s",
|
||||
self.entity_id,
|
||||
self.state,
|
||||
)
|
||||
except HomeConnectError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_option",
|
||||
translation_placeholders=get_dict_from_home_connect_error(err),
|
||||
) from err
|
||||
|
||||
@property
|
||||
def bsh_key(self) -> OptionKey:
|
||||
"""Return the BSH key."""
|
||||
return cast(OptionKey, self.entity_description.key)
|
||||
|
@ -208,6 +208,39 @@
|
||||
},
|
||||
"door-assistant_freezer": {
|
||||
"default": "mdi:door"
|
||||
},
|
||||
"silence_on_demand": {
|
||||
"default": "mdi:volume-mute",
|
||||
"state": {
|
||||
"on": "mdi:volume-mute",
|
||||
"off": "mdi:volume-high"
|
||||
}
|
||||
},
|
||||
"half_load": {
|
||||
"default": "mdi:fraction-one-half"
|
||||
},
|
||||
"hygiene_plus": {
|
||||
"default": "mdi:silverware-clean"
|
||||
},
|
||||
"eco_dry": {
|
||||
"default": "mdi:sprout"
|
||||
},
|
||||
"fast_pre_heat": {
|
||||
"default": "mdi:fire"
|
||||
},
|
||||
"i_dos_1_active": {
|
||||
"default": "mdi:numeric-1-circle"
|
||||
},
|
||||
"i_dos_2_active": {
|
||||
"default": "mdi:numeric-2-circle"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"start_in_relative": {
|
||||
"default": "mdi:progress-clock"
|
||||
},
|
||||
"finish_in_relative": {
|
||||
"default": "mdi:progress-clock"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from aiohomeconnect.model import GetSetting, SettingKey
|
||||
from aiohomeconnect.model import GetSetting, OptionKey, SettingKey
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
|
||||
from homeassistant.components.number import (
|
||||
@ -11,6 +11,7 @@ from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.const import UnitOfTemperature, UnitOfTime, UnitOfVolume
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@ -24,11 +25,17 @@ from .const import (
|
||||
SVE_TRANSLATION_PLACEHOLDER_VALUE,
|
||||
)
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UNIT_MAP = {
|
||||
"seconds": UnitOfTime.SECONDS,
|
||||
"ml": UnitOfVolume.MILLILITERS,
|
||||
"°C": UnitOfTemperature.CELSIUS,
|
||||
"°F": UnitOfTemperature.FAHRENHEIT,
|
||||
}
|
||||
|
||||
NUMBERS = (
|
||||
NumberEntityDescription(
|
||||
@ -88,6 +95,32 @@ NUMBERS = (
|
||||
),
|
||||
)
|
||||
|
||||
NUMBER_OPTIONS = (
|
||||
NumberEntityDescription(
|
||||
key=OptionKey.BSH_COMMON_DURATION,
|
||||
translation_key="duration",
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key=OptionKey.BSH_COMMON_FINISH_IN_RELATIVE,
|
||||
translation_key="finish_in_relative",
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key=OptionKey.BSH_COMMON_START_IN_RELATIVE,
|
||||
translation_key="start_in_relative",
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FILL_QUANTITY,
|
||||
translation_key="fill_quantity",
|
||||
device_class=NumberDeviceClass.VOLUME,
|
||||
native_step=1,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key=OptionKey.COOKING_OVEN_SETPOINT_TEMPERATURE,
|
||||
translation_key="setpoint_temperature",
|
||||
device_class=NumberDeviceClass.TEMPERATURE,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -101,6 +134,18 @@ def _get_entities_for_appliance(
|
||||
]
|
||||
|
||||
|
||||
def _get_option_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectOptionEntity]:
|
||||
"""Get a list of currently available option entities."""
|
||||
return [
|
||||
HomeConnectOptionNumberEntity(entry.runtime_data, appliance, description)
|
||||
for description in NUMBER_OPTIONS
|
||||
if description.key in appliance.options
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -111,6 +156,7 @@ async def async_setup_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
_get_option_entities_for_appliance,
|
||||
)
|
||||
|
||||
|
||||
@ -184,3 +230,44 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
|
||||
or not hasattr(self, "_attr_native_step")
|
||||
):
|
||||
await self.async_fetch_constraints()
|
||||
|
||||
|
||||
class HomeConnectOptionNumberEntity(HomeConnectOptionEntity, NumberEntity):
|
||||
"""Number option class for Home Connect."""
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set the native value of the entity."""
|
||||
await self.async_set_option(value)
|
||||
|
||||
def update_native_value(self) -> None:
|
||||
"""Set the value of the entity."""
|
||||
self._attr_native_value = cast(float | None, self.option_value)
|
||||
option_definition = self.appliance.options.get(self.bsh_key)
|
||||
if option_definition:
|
||||
if option_definition.unit:
|
||||
candidate_unit = UNIT_MAP.get(
|
||||
option_definition.unit, option_definition.unit
|
||||
)
|
||||
if (
|
||||
not hasattr(self, "_attr_native_unit_of_measurement")
|
||||
or candidate_unit != self._attr_native_unit_of_measurement
|
||||
):
|
||||
self._attr_native_unit_of_measurement = candidate_unit
|
||||
self.__dict__.pop("unit_of_measurement", None)
|
||||
option_constraints = option_definition.constraints
|
||||
if option_constraints:
|
||||
if (
|
||||
not hasattr(self, "_attr_native_min_value")
|
||||
or self._attr_native_min_value != option_constraints.min
|
||||
) and option_constraints.min:
|
||||
self._attr_native_min_value = option_constraints.min
|
||||
if (
|
||||
not hasattr(self, "_attr_native_max_value")
|
||||
or self._attr_native_max_value != option_constraints.max
|
||||
) and option_constraints.max:
|
||||
self._attr_native_max_value = option_constraints.max
|
||||
if (
|
||||
not hasattr(self, "_attr_native_step")
|
||||
or self._attr_native_step != option_constraints.step_size
|
||||
) and option_constraints.step_size:
|
||||
self._attr_native_step = option_constraints.step_size
|
||||
|
@ -5,7 +5,7 @@ from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from aiohomeconnect.client import Client as HomeConnectClient
|
||||
from aiohomeconnect.model import EventKey, ProgramKey
|
||||
from aiohomeconnect.model import EventKey, OptionKey, ProgramKey
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.program import Execution
|
||||
|
||||
@ -17,17 +17,32 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
APPLIANCES_WITH_PROGRAMS,
|
||||
BEAN_AMOUNT_OPTIONS,
|
||||
BEAN_CONTAINER_OPTIONS,
|
||||
CLEANING_MODE_OPTIONS,
|
||||
COFFEE_MILK_RATIO_OPTIONS,
|
||||
COFFEE_TEMPERATURE_OPTIONS,
|
||||
DOMAIN,
|
||||
DRYING_TARGET_OPTIONS,
|
||||
FLOW_RATE_OPTIONS,
|
||||
HOT_WATER_TEMPERATURE_OPTIONS,
|
||||
INTENSIVE_LEVEL_OPTIONS,
|
||||
PROGRAMS_TRANSLATION_KEYS_MAP,
|
||||
REFERENCE_MAP_ID_OPTIONS,
|
||||
SPIN_SPEED_OPTIONS,
|
||||
SVE_TRANSLATION_PLACEHOLDER_PROGRAM,
|
||||
TEMPERATURE_OPTIONS,
|
||||
TRANSLATION_KEYS_PROGRAMS_MAP,
|
||||
VARIO_PERFECT_OPTIONS,
|
||||
VENTING_LEVEL_OPTIONS,
|
||||
WARMING_LEVEL_OPTIONS,
|
||||
)
|
||||
from .coordinator import (
|
||||
HomeConnectApplianceData,
|
||||
HomeConnectConfigEntry,
|
||||
HomeConnectCoordinator,
|
||||
)
|
||||
from .entity import HomeConnectEntity
|
||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
|
||||
@ -44,6 +59,16 @@ class HomeConnectProgramSelectEntityDescription(
|
||||
error_translation_key: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class HomeConnectSelectOptionEntityDescription(
|
||||
SelectEntityDescription,
|
||||
):
|
||||
"""Entity Description class for options that have enumeration values."""
|
||||
|
||||
translation_key_values: dict[str, str]
|
||||
values_translation_key: dict[str, str]
|
||||
|
||||
|
||||
PROGRAM_SELECT_ENTITY_DESCRIPTIONS = (
|
||||
HomeConnectProgramSelectEntityDescription(
|
||||
key=EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
@ -65,6 +90,159 @@ PROGRAM_SELECT_ENTITY_DESCRIPTIONS = (
|
||||
),
|
||||
)
|
||||
|
||||
PROGRAM_SELECT_OPTION_ENTITY_DESCRIPTIONS = (
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_REFERENCE_MAP_ID,
|
||||
translation_key="reference_map_id",
|
||||
options=list(REFERENCE_MAP_ID_OPTIONS.keys()),
|
||||
translation_key_values=REFERENCE_MAP_ID_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in REFERENCE_MAP_ID_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_CLEANING_MODE,
|
||||
translation_key="reference_map_id",
|
||||
options=list(CLEANING_MODE_OPTIONS.keys()),
|
||||
translation_key_values=CLEANING_MODE_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in CLEANING_MODE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEAN_AMOUNT,
|
||||
translation_key="bean_amount",
|
||||
options=list(BEAN_AMOUNT_OPTIONS.keys()),
|
||||
translation_key_values=BEAN_AMOUNT_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in BEAN_AMOUNT_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_COFFEE_TEMPERATURE,
|
||||
translation_key="coffee_temperature",
|
||||
options=list(COFFEE_TEMPERATURE_OPTIONS.keys()),
|
||||
translation_key_values=COFFEE_TEMPERATURE_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in COFFEE_TEMPERATURE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEAN_CONTAINER_SELECTION,
|
||||
translation_key="bean_container",
|
||||
options=list(BEAN_CONTAINER_OPTIONS.keys()),
|
||||
translation_key_values=BEAN_CONTAINER_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in BEAN_CONTAINER_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FLOW_RATE,
|
||||
translation_key="flow_rate",
|
||||
options=list(FLOW_RATE_OPTIONS.keys()),
|
||||
translation_key_values=FLOW_RATE_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in FLOW_RATE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_COFFEE_MILK_RATIO,
|
||||
translation_key="coffee_milk_ratio",
|
||||
options=list(COFFEE_MILK_RATIO_OPTIONS.keys()),
|
||||
translation_key_values=COFFEE_MILK_RATIO_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in FLOW_RATE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_HOT_WATER_TEMPERATURE,
|
||||
translation_key="hot_water_temperature",
|
||||
options=list(HOT_WATER_TEMPERATURE_OPTIONS.keys()),
|
||||
translation_key_values=HOT_WATER_TEMPERATURE_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in HOT_WATER_TEMPERATURE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_DRYER_DRYING_TARGET,
|
||||
translation_key="drying_target",
|
||||
options=list(DRYING_TARGET_OPTIONS.keys()),
|
||||
translation_key_values=DRYING_TARGET_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in DRYING_TARGET_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.COOKING_COMMON_HOOD_VENTING_LEVEL,
|
||||
translation_key="venting_level",
|
||||
options=list(VENTING_LEVEL_OPTIONS.keys()),
|
||||
translation_key_values=VENTING_LEVEL_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in VENTING_LEVEL_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.COOKING_COMMON_HOOD_INTENSIVE_LEVEL,
|
||||
translation_key="intensive_level",
|
||||
options=list(INTENSIVE_LEVEL_OPTIONS.keys()),
|
||||
translation_key_values=INTENSIVE_LEVEL_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in INTENSIVE_LEVEL_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.COOKING_OVEN_WARMING_LEVEL,
|
||||
translation_key="warming_level",
|
||||
options=list(WARMING_LEVEL_OPTIONS.keys()),
|
||||
translation_key_values=WARMING_LEVEL_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in WARMING_LEVEL_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE,
|
||||
translation_key="washer_temperature",
|
||||
options=list(TEMPERATURE_OPTIONS.keys()),
|
||||
translation_key_values=TEMPERATURE_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in TEMPERATURE_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_WASHER_SPIN_SPEED,
|
||||
translation_key="spin_speed",
|
||||
options=list(SPIN_SPEED_OPTIONS.keys()),
|
||||
translation_key_values=SPIN_SPEED_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in SPIN_SPEED_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
HomeConnectSelectOptionEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_COMMON_VARIO_PERFECT,
|
||||
translation_key="vario_perfect",
|
||||
options=list(VARIO_PERFECT_OPTIONS.keys()),
|
||||
translation_key_values=VARIO_PERFECT_OPTIONS,
|
||||
values_translation_key={
|
||||
value: translation_key
|
||||
for translation_key, value in VARIO_PERFECT_OPTIONS.items()
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -81,6 +259,18 @@ def _get_entities_for_appliance(
|
||||
)
|
||||
|
||||
|
||||
def _get_option_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectOptionEntity]:
|
||||
"""Get a list of entities."""
|
||||
return [
|
||||
HomeConnectSelectOptionEntity(entry.runtime_data, appliance, desc)
|
||||
for desc in PROGRAM_SELECT_OPTION_ENTITY_DESCRIPTIONS
|
||||
if desc.key in appliance.options
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -91,6 +281,7 @@ async def async_setup_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
_get_option_entities_for_appliance,
|
||||
)
|
||||
|
||||
|
||||
@ -148,3 +339,53 @@ class HomeConnectProgramSelectEntity(HomeConnectEntity, SelectEntity):
|
||||
SVE_TRANSLATION_PLACEHOLDER_PROGRAM: program_key.value,
|
||||
},
|
||||
) from err
|
||||
|
||||
|
||||
class HomeConnectSelectOptionEntity(HomeConnectOptionEntity, SelectEntity):
|
||||
"""Select option class for Home Connect."""
|
||||
|
||||
entity_description: HomeConnectSelectOptionEntityDescription
|
||||
_original_option_keys: set[str | None]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HomeConnectCoordinator,
|
||||
appliance: HomeConnectApplianceData,
|
||||
desc: HomeConnectSelectOptionEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self._original_option_keys = set(desc.values_translation_key.keys())
|
||||
super().__init__(
|
||||
coordinator,
|
||||
appliance,
|
||||
desc,
|
||||
)
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Select new option."""
|
||||
await self.async_set_option(
|
||||
self.entity_description.translation_key_values[option]
|
||||
)
|
||||
|
||||
def update_native_value(self) -> None:
|
||||
"""Set the value of the entity."""
|
||||
self._attr_current_option = (
|
||||
self.entity_description.values_translation_key.get(
|
||||
cast(str, self.option_value), None
|
||||
)
|
||||
if self.option_value is not None
|
||||
else None
|
||||
)
|
||||
if (
|
||||
(option_definition := self.appliance.options.get(self.bsh_key))
|
||||
and (option_constraints := option_definition.constraints)
|
||||
and option_constraints.allowed_values
|
||||
and self._original_option_keys != set(option_constraints.allowed_values)
|
||||
):
|
||||
self._original_option_keys = set(option_constraints.allowed_values)
|
||||
self._attr_options = [
|
||||
self.entity_description.values_translation_key[option]
|
||||
for option in self._original_option_keys
|
||||
if option is not None
|
||||
]
|
||||
self.__dict__.pop("options", None)
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime, UnitOfVolume
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util, slugify
|
||||
@ -56,12 +56,6 @@ BSH_PROGRAM_SENSORS = (
|
||||
"WasherDryer",
|
||||
),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=EventKey.BSH_COMMON_OPTION_DURATION,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
appliance_types=("Oven",),
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=EventKey.BSH_COMMON_OPTION_PROGRAM_PROGRESS,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
|
@ -98,6 +98,9 @@
|
||||
},
|
||||
"required_program_or_one_option_at_least": {
|
||||
"message": "A program or at least one of the possible options for a program should be specified"
|
||||
},
|
||||
"set_option": {
|
||||
"message": "Error setting the option for the program: {error}"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
@ -859,6 +862,21 @@
|
||||
},
|
||||
"washer_i_dos_2_base_level": {
|
||||
"name": "i-Dos 2 base level"
|
||||
},
|
||||
"duration": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::b_s_h_common_option_duration::name%]"
|
||||
},
|
||||
"start_in_relative": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::b_s_h_common_option_start_in_relative::name%]"
|
||||
},
|
||||
"finish_in_relative": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::b_s_h_common_option_finish_in_relative::name%]"
|
||||
},
|
||||
"fill_quantity": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_fill_quantity::name%]"
|
||||
},
|
||||
"setpoint_temperature": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_setpoint_temperature::name%]"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
@ -1179,6 +1197,200 @@
|
||||
"laundry_care_washer_dryer_program_wash_and_dry_60": "[%key:component::home_connect::selector::programs::options::laundry_care_washer_dryer_program_wash_and_dry_60%]",
|
||||
"laundry_care_washer_dryer_program_wash_and_dry_90": "[%key:component::home_connect::selector::programs::options::laundry_care_washer_dryer_program_wash_and_dry_90%]"
|
||||
}
|
||||
},
|
||||
"reference_map_id": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_cleaning_robot_option_reference_map_id::name%]",
|
||||
"state": {
|
||||
"consumer_products_cleaning_robot_enum_type_available_maps_temp_map": "[%key:component::home_connect::selector::available_maps::options::consumer_products_cleaning_robot_enum_type_available_maps_temp_map%]",
|
||||
"consumer_products_cleaning_robot_enum_type_available_maps_map1": "[%key:component::home_connect::selector::available_maps::options::consumer_products_cleaning_robot_enum_type_available_maps_map1%]",
|
||||
"consumer_products_cleaning_robot_enum_type_available_maps_map2": "[%key:component::home_connect::selector::available_maps::options::consumer_products_cleaning_robot_enum_type_available_maps_map2%]",
|
||||
"consumer_products_cleaning_robot_enum_type_available_maps_map3": "[%key:component::home_connect::selector::available_maps::options::consumer_products_cleaning_robot_enum_type_available_maps_map3%]"
|
||||
}
|
||||
},
|
||||
"cleaning_mode": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_cleaning_robot_option_cleaning_mode::name%]",
|
||||
"state": {
|
||||
"consumer_products_cleaning_robot_enum_type_cleaning_modes_silent": "[%key:component::home_connect::selector::cleaning_mode::options::consumer_products_cleaning_robot_enum_type_cleaning_modes_silent%]",
|
||||
"consumer_products_cleaning_robot_enum_type_cleaning_modes_standard": "[%key:component::home_connect::selector::cleaning_mode::options::consumer_products_cleaning_robot_enum_type_cleaning_modes_standard%]",
|
||||
"consumer_products_cleaning_robot_enum_type_cleaning_modes_power": "[%key:component::home_connect::selector::cleaning_mode::options::consumer_products_cleaning_robot_enum_type_cleaning_modes_power%]"
|
||||
}
|
||||
},
|
||||
"bean_amount": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_bean_amount::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_very_mild": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_very_mild%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_mild": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_mild%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_mild_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_mild_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_normal": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_normal%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_normal_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_normal_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_strong": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_strong%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_strong_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_strong_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_very_strong": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_very_strong%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_very_strong_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_very_strong_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_extra_strong": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_extra_strong%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_double_shot": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_double_shot%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_triple_shot": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_triple_shot%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_triple_shot_plus": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_triple_shot_plus%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_amount_coffee_ground": "[%key:component::home_connect::selector::bean_amount::options::consumer_products_coffee_maker_enum_type_bean_amount_coffee_ground%]"
|
||||
}
|
||||
},
|
||||
"coffee_temperature": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_coffee_temperature::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_88_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_88_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_90_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_90_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_92_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_92_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_94_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_94_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_95_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_95_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_temperature_96_c": "[%key:component::home_connect::selector::coffee_temperature::options::consumer_products_coffee_maker_enum_type_coffee_temperature_96_c%]"
|
||||
}
|
||||
},
|
||||
"bean_container": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_bean_container::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_bean_container_selection_right": "[%key:component::home_connect::selector::bean_container::options::consumer_products_coffee_maker_enum_type_bean_container_selection_right%]",
|
||||
"consumer_products_coffee_maker_enum_type_bean_container_selection_left": "[%key:component::home_connect::selector::bean_container::options::consumer_products_coffee_maker_enum_type_bean_container_selection_left%]"
|
||||
}
|
||||
},
|
||||
"flow_rate": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_flow_rate::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_flow_rate_normal": "[%key:component::home_connect::selector::flow_rate::options::consumer_products_coffee_maker_enum_type_flow_rate_normal%]",
|
||||
"consumer_products_coffee_maker_enum_type_flow_rate_intense": "[%key:component::home_connect::selector::flow_rate::options::consumer_products_coffee_maker_enum_type_flow_rate_intense%]",
|
||||
"consumer_products_coffee_maker_enum_type_flow_rate_intense_plus": "[%key:component::home_connect::selector::flow_rate::options::consumer_products_coffee_maker_enum_type_flow_rate_intense_plus%]"
|
||||
}
|
||||
},
|
||||
"coffee_milk_ratio": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_coffee_milk_ratio::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_10_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_10_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_20_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_20_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_25_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_25_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_30_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_30_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_40_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_40_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_50_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_50_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_55_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_55_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_60_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_60_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_65_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_65_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_67_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_67_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_70_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_70_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_75_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_75_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_80_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_80_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_85_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_85_percent%]",
|
||||
"consumer_products_coffee_maker_enum_type_coffee_milk_ratio_90_percent": "[%key:component::home_connect::selector::coffee_milk_ratio::options::consumer_products_coffee_maker_enum_type_coffee_milk_ratio_90_percent%]"
|
||||
}
|
||||
},
|
||||
"hot_water_temperature": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_hot_water_temperature::name%]",
|
||||
"state": {
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_white_tea": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_white_tea%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_green_tea": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_green_tea%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_black_tea": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_black_tea%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_50_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_50_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_55_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_55_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_60_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_60_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_65_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_65_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_70_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_70_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_75_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_75_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_80_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_80_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_85_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_85_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_90_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_90_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_95_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_95_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_97_c": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_97_c%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_122_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_122_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_131_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_131_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_140_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_140_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_149_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_149_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_158_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_158_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_167_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_167_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_176_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_176_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_185_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_185_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_194_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_194_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_203_f": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_203_f%]",
|
||||
"consumer_products_coffee_maker_enum_type_hot_water_temperature_max": "[%key:component::home_connect::selector::hot_water_temperature::options::consumer_products_coffee_maker_enum_type_hot_water_temperature_max%]"
|
||||
}
|
||||
},
|
||||
"drying_target": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_dryer_option_drying_target::name%]",
|
||||
"state": {
|
||||
"laundry_care_dryer_enum_type_drying_target_iron_dry": "[%key:component::home_connect::selector::drying_target::options::laundry_care_dryer_enum_type_drying_target_iron_dry%]",
|
||||
"laundry_care_dryer_enum_type_drying_target_gentle_dry": "[%key:component::home_connect::selector::drying_target::options::laundry_care_dryer_enum_type_drying_target_gentle_dry%]",
|
||||
"laundry_care_dryer_enum_type_drying_target_cupboard_dry": "[%key:component::home_connect::selector::drying_target::options::laundry_care_dryer_enum_type_drying_target_cupboard_dry%]",
|
||||
"laundry_care_dryer_enum_type_drying_target_cupboard_dry_plus": "[%key:component::home_connect::selector::drying_target::options::laundry_care_dryer_enum_type_drying_target_cupboard_dry_plus%]",
|
||||
"laundry_care_dryer_enum_type_drying_target_extra_dry": "[%key:component::home_connect::selector::drying_target::options::laundry_care_dryer_enum_type_drying_target_extra_dry%]"
|
||||
}
|
||||
},
|
||||
"venting_level": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_hood_option_venting_level::name%]",
|
||||
"state": {
|
||||
"cooking_hood_enum_type_stage_fan_off": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_off%]",
|
||||
"cooking_hood_enum_type_stage_fan_stage01": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage01%]",
|
||||
"cooking_hood_enum_type_stage_fan_stage02": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage02%]",
|
||||
"cooking_hood_enum_type_stage_fan_stage03": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage03%]",
|
||||
"cooking_hood_enum_type_stage_fan_stage04": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage04%]",
|
||||
"cooking_hood_enum_type_stage_fan_stage05": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage05%]"
|
||||
}
|
||||
},
|
||||
"intensive_level": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_hood_option_intensive_level::name%]",
|
||||
"state": {
|
||||
"cooking_hood_enum_type_intensive_stage_intensive_stage_off": "[%key:component::home_connect::selector::intensive_level::options::cooking_hood_enum_type_intensive_stage_intensive_stage_off%]",
|
||||
"cooking_hood_enum_type_intensive_stage_intensive_stage1": "[%key:component::home_connect::selector::intensive_level::options::cooking_hood_enum_type_intensive_stage_intensive_stage1%]",
|
||||
"cooking_hood_enum_type_intensive_stage_intensive_stage2": "[%key:component::home_connect::selector::intensive_level::options::cooking_hood_enum_type_intensive_stage_intensive_stage2%]"
|
||||
}
|
||||
},
|
||||
"warming_level": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_warming_level::name%]",
|
||||
"state": {
|
||||
"cooking_oven_enum_type_warming_level_low": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_low%]",
|
||||
"cooking_oven_enum_type_warming_level_medium": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_medium%]",
|
||||
"cooking_oven_enum_type_warming_level_high": "[%key:component::home_connect::selector::warming_level::options::cooking_oven_enum_type_warming_level_high%]"
|
||||
}
|
||||
},
|
||||
"washer_temperature": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_temperature::name%]",
|
||||
"state": {
|
||||
"laundry_care_washer_enum_type_temperature_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_cold%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c20": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c20%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c30": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c30%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c40": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c40%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c50": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c50%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c60": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c60%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c70": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c70%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c80": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c80%]",
|
||||
"laundry_care_washer_enum_type_temperature_g_c90": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c90%]",
|
||||
"laundry_care_washer_enum_type_temperature_ul_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_cold%]",
|
||||
"laundry_care_washer_enum_type_temperature_ul_warm": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_warm%]",
|
||||
"laundry_care_washer_enum_type_temperature_ul_hot": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_hot%]",
|
||||
"laundry_care_washer_enum_type_temperature_ul_extra_hot": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_extra_hot%]"
|
||||
}
|
||||
},
|
||||
"spin_speed": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_spin_speed::name%]",
|
||||
"state": {
|
||||
"laundry_care_washer_enum_type_spin_speed_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_off%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m400%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m600%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m800%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1000%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1200%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1400%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1600%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_off%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_ul_high": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_high%]"
|
||||
}
|
||||
},
|
||||
"vario_perfect": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_vario_perfect::name%]",
|
||||
"state": {
|
||||
"laundry_care_common_enum_type_vario_perfect_off": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_off%]",
|
||||
"laundry_care_common_enum_type_vario_perfect_eco_perfect": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_eco_perfect%]",
|
||||
"laundry_care_common_enum_type_vario_perfect_speed_perfect": "[%key:component::home_connect::selector::vario_perfect::options::laundry_care_common_enum_type_vario_perfect_speed_perfect%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@ -1365,6 +1577,45 @@
|
||||
},
|
||||
"door_assistant_freezer": {
|
||||
"name": "Freezer door assistant"
|
||||
},
|
||||
"multiple_beverages": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::consumer_products_coffee_maker_option_multiple_beverages::name%]"
|
||||
},
|
||||
"intensiv_zone": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_intensiv_zone::name%]"
|
||||
},
|
||||
"brilliance_dry": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_brilliance_dry::name%]"
|
||||
},
|
||||
"vario_speed_plus": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_vario_speed_plus::name%]"
|
||||
},
|
||||
"silence_on_demand": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_silence_on_demand::name%]"
|
||||
},
|
||||
"half_load": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_half_load::name%]"
|
||||
},
|
||||
"extra_dry": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_extra_dry::name%]"
|
||||
},
|
||||
"hygiene_plus": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_hygiene_plus::name%]"
|
||||
},
|
||||
"eco_dry": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_eco_dry::name%]"
|
||||
},
|
||||
"zeolite_dry": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::dishcare_dishwasher_option_zeolite_dry::name%]"
|
||||
},
|
||||
"fast_pre_heat": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_oven_option_fast_pre_heat::name%]"
|
||||
},
|
||||
"i_dos1_active": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_i_dos1_active::name%]"
|
||||
},
|
||||
"i_dos2_active": {
|
||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_i_dos2_active::name%]"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from aiohomeconnect.model import EventKey, ProgramKey, SettingKey
|
||||
from aiohomeconnect.model import EventKey, OptionKey, ProgramKey, SettingKey
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.program import EnumerateProgram
|
||||
|
||||
@ -37,7 +37,7 @@ from .coordinator import (
|
||||
HomeConnectConfigEntry,
|
||||
HomeConnectCoordinator,
|
||||
)
|
||||
from .entity import HomeConnectEntity
|
||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -100,6 +100,61 @@ POWER_SWITCH_DESCRIPTION = SwitchEntityDescription(
|
||||
translation_key="power",
|
||||
)
|
||||
|
||||
SWITCH_OPTIONS = (
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_MULTIPLE_BEVERAGES,
|
||||
translation_key="multiple_beverages",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_INTENSIV_ZONE,
|
||||
translation_key="intensiv_zone",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_BRILLIANCE_DRY,
|
||||
translation_key="brilliance_dry",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_VARIO_SPEED_PLUS,
|
||||
translation_key="vario_speed_plus",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_SILENCE_ON_DEMAND,
|
||||
translation_key="silence_on_demand",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_HALF_LOAD,
|
||||
translation_key="half_load",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_EXTRA_DRY,
|
||||
translation_key="extra_dry",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_HYGIENE_PLUS,
|
||||
translation_key="hygiene_plus",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_ECO_DRY,
|
||||
translation_key="eco_dry",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.DISHCARE_DISHWASHER_ZEOLITE_DRY,
|
||||
translation_key="zeolite_dry",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.COOKING_OVEN_FAST_PRE_HEAT,
|
||||
translation_key="fast_pre_heat",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_WASHER_I_DOS_1_ACTIVE,
|
||||
translation_key="i_dos1_active",
|
||||
),
|
||||
SwitchEntityDescription(
|
||||
key=OptionKey.LAUNDRY_CARE_WASHER_I_DOS_2_ACTIVE,
|
||||
translation_key="i_dos2_active",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -123,10 +178,21 @@ def _get_entities_for_appliance(
|
||||
for description in SWITCHES
|
||||
if description.key in appliance.settings
|
||||
)
|
||||
|
||||
return entities
|
||||
|
||||
|
||||
def _get_option_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectOptionEntity]:
|
||||
"""Get a list of currently available option entities."""
|
||||
return [
|
||||
HomeConnectSwitchOptionEntity(entry.runtime_data, appliance, description)
|
||||
for description in SWITCH_OPTIONS
|
||||
if description.key in appliance.options
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
@ -137,6 +203,7 @@ async def async_setup_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
_get_option_entities_for_appliance,
|
||||
)
|
||||
|
||||
|
||||
@ -403,3 +470,19 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
||||
self.power_off_state = BSH_POWER_STANDBY
|
||||
else:
|
||||
self.power_off_state = None
|
||||
|
||||
|
||||
class HomeConnectSwitchOptionEntity(HomeConnectOptionEntity, SwitchEntity):
|
||||
"""Switch option class for Home Connect."""
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the option."""
|
||||
await self.async_set_option(True)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the option."""
|
||||
await self.async_set_option(False)
|
||||
|
||||
def update_native_value(self) -> None:
|
||||
"""Set the value of the entity."""
|
||||
self._attr_is_on = cast(bool | None, self.option_value)
|
||||
|
@ -23,6 +23,8 @@ from aiohomeconnect.model import (
|
||||
HomeAppliance,
|
||||
Option,
|
||||
Program,
|
||||
ProgramDefinition,
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
@ -339,6 +341,29 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
|
||||
mock.add_events = add_events
|
||||
|
||||
async def set_program_option_side_effect(ha_id: str, *_, **kwargs) -> None:
|
||||
event_key = EventKey(kwargs["option_key"])
|
||||
await event_queue.put(
|
||||
[
|
||||
EventMessage(
|
||||
ha_id,
|
||||
EventType.NOTIFY,
|
||||
ArrayOfEvents(
|
||||
[
|
||||
Event(
|
||||
key=event_key,
|
||||
raw_key=event_key.value,
|
||||
timestamp=0,
|
||||
level="",
|
||||
handling="",
|
||||
value=kwargs["value"],
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
async def stream_all_events() -> AsyncGenerator[EventMessage]:
|
||||
"""Mock stream_all_events."""
|
||||
while True:
|
||||
@ -380,6 +405,17 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
mock.get_status = AsyncMock(return_value=copy.deepcopy(MOCK_STATUS))
|
||||
mock.get_all_programs = AsyncMock(side_effect=_get_all_programs_side_effect)
|
||||
mock.put_command = AsyncMock()
|
||||
mock.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(ProgramKey.UNKNOWN, options=[])
|
||||
)
|
||||
mock.get_active_program_options = AsyncMock(return_value=ArrayOfOptions([]))
|
||||
mock.get_selected_program_options = AsyncMock(return_value=ArrayOfOptions([]))
|
||||
mock.set_active_program_option = AsyncMock(
|
||||
side_effect=set_program_option_side_effect
|
||||
)
|
||||
mock.set_selected_program_option = AsyncMock(
|
||||
side_effect=set_program_option_side_effect
|
||||
)
|
||||
|
||||
mock.side_effect = mock
|
||||
return mock
|
||||
@ -420,6 +456,11 @@ def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
||||
mock.get_status = AsyncMock(side_effect=exception)
|
||||
mock.get_all_programs = AsyncMock(side_effect=exception)
|
||||
mock.put_command = AsyncMock(side_effect=exception)
|
||||
mock.get_available_program = AsyncMock(side_effect=exception)
|
||||
mock.get_active_program_options = AsyncMock(side_effect=exception)
|
||||
mock.get_selected_program_options = AsyncMock(side_effect=exception)
|
||||
mock.set_active_program_option = AsyncMock(side_effect=exception)
|
||||
mock.set_selected_program_option = AsyncMock(side_effect=exception)
|
||||
|
||||
return mock
|
||||
|
||||
|
@ -124,6 +124,11 @@
|
||||
"key": "BSH.Common.Setting.ChildLock",
|
||||
"value": false,
|
||||
"type": "Boolean"
|
||||
},
|
||||
{
|
||||
"key": "LaundryCare.Washer.Setting.IDos2BaseLevel",
|
||||
"value": 0,
|
||||
"type": "Integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -272,6 +272,7 @@
|
||||
'settings': dict({
|
||||
'BSH.Common.Setting.ChildLock': False,
|
||||
'BSH.Common.Setting.PowerState': 'BSH.Common.EnumType.PowerState.On',
|
||||
'LaundryCare.Washer.Setting.IDos2BaseLevel': 0,
|
||||
}),
|
||||
'status': dict({
|
||||
'BSH.Common.Status.DoorState': 'BSH.Common.EnumType.DoorState.Closed',
|
||||
|
299
tests/components/home_connect/test_entity.py
Normal file
299
tests/components/home_connect/test_entity.py
Normal file
@ -0,0 +1,299 @@
|
||||
"""Tests for Home Connect entity base classes."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfPrograms,
|
||||
Event,
|
||||
EventKey,
|
||||
EventMessage,
|
||||
EventType,
|
||||
Option,
|
||||
OptionKey,
|
||||
Program,
|
||||
ProgramDefinition,
|
||||
ProgramKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import (
|
||||
ActiveProgramNotSetError,
|
||||
HomeConnectError,
|
||||
SelectedProgramNotSetError,
|
||||
)
|
||||
from aiohomeconnect.model.program import (
|
||||
ProgramDefinitionConstraints,
|
||||
ProgramDefinitionOption,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[str]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.SWITCH]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("array_of_programs_program_arg", "event_key"),
|
||||
[
|
||||
(
|
||||
"active",
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
),
|
||||
(
|
||||
"selected",
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"appliance_ha_id",
|
||||
"option_entity_id",
|
||||
"options_state_stage_1",
|
||||
"options_availability_stage_2",
|
||||
"option_without_default",
|
||||
"option_without_constraints",
|
||||
),
|
||||
[
|
||||
(
|
||||
"Dishwasher",
|
||||
{
|
||||
OptionKey.DISHCARE_DISHWASHER_HALF_LOAD: "switch.dishwasher_half_load",
|
||||
OptionKey.DISHCARE_DISHWASHER_SILENCE_ON_DEMAND: "switch.dishwasher_silence_on_demand",
|
||||
OptionKey.DISHCARE_DISHWASHER_ECO_DRY: "switch.dishwasher_eco_dry",
|
||||
},
|
||||
[(STATE_ON, True), (STATE_OFF, False), (None, None)],
|
||||
[False, True, True],
|
||||
(
|
||||
OptionKey.DISHCARE_DISHWASHER_HYGIENE_PLUS,
|
||||
"switch.dishwasher_hygiene_plus",
|
||||
),
|
||||
(OptionKey.DISHCARE_DISHWASHER_EXTRA_DRY, "switch.dishwasher_extra_dry"),
|
||||
)
|
||||
],
|
||||
indirect=["appliance_ha_id"],
|
||||
)
|
||||
async def test_program_options_retrieval(
|
||||
array_of_programs_program_arg: str,
|
||||
event_key: EventKey,
|
||||
appliance_ha_id: str,
|
||||
option_entity_id: dict[OptionKey, str],
|
||||
options_state_stage_1: list[tuple[str, bool | None]],
|
||||
options_availability_stage_2: list[bool],
|
||||
option_without_default: tuple[OptionKey, str],
|
||||
option_without_constraints: tuple[OptionKey, str],
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test that the options are correctly retrieved at the start and updated on program updates."""
|
||||
original_get_all_programs_mock = client.get_all_programs.side_effect
|
||||
options_values = [
|
||||
Option(
|
||||
option_key,
|
||||
value,
|
||||
)
|
||||
for option_key, (_, value) in zip(
|
||||
option_entity_id.keys(), options_state_stage_1, strict=True
|
||||
)
|
||||
if value is not None
|
||||
]
|
||||
|
||||
async def get_all_programs_with_options_mock(ha_id: str) -> ArrayOfPrograms:
|
||||
if ha_id != appliance_ha_id:
|
||||
return await original_get_all_programs_mock(ha_id)
|
||||
|
||||
array_of_programs: ArrayOfPrograms = await original_get_all_programs_mock(ha_id)
|
||||
return ArrayOfPrograms(
|
||||
**(
|
||||
{
|
||||
"programs": array_of_programs.programs,
|
||||
array_of_programs_program_arg: Program(
|
||||
array_of_programs.programs[0].key, options=options_values
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
client.get_all_programs = AsyncMock(side_effect=get_all_programs_with_options_mock)
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
option_key,
|
||||
"Boolean",
|
||||
constraints=ProgramDefinitionConstraints(
|
||||
default=False,
|
||||
),
|
||||
)
|
||||
for option_key, (_, value) in zip(
|
||||
option_entity_id.keys(), options_state_stage_1, strict=True
|
||||
)
|
||||
if value is not None
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id, (state, _) in zip(
|
||||
option_entity_id.values(), options_state_stage_1, strict=True
|
||||
):
|
||||
if state is not None:
|
||||
assert hass.states.is_state(entity_id, state)
|
||||
else:
|
||||
assert not hass.states.get(entity_id)
|
||||
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
*[
|
||||
ProgramDefinitionOption(
|
||||
option_key,
|
||||
"Boolean",
|
||||
constraints=ProgramDefinitionConstraints(
|
||||
default=False,
|
||||
),
|
||||
)
|
||||
for option_key, available in zip(
|
||||
option_entity_id.keys(),
|
||||
options_availability_stage_2,
|
||||
strict=True,
|
||||
)
|
||||
if available
|
||||
],
|
||||
ProgramDefinitionOption(
|
||||
option_without_default[0],
|
||||
"Boolean",
|
||||
constraints=ProgramDefinitionConstraints(),
|
||||
),
|
||||
ProgramDefinitionOption(
|
||||
option_without_constraints[0],
|
||||
"Boolean",
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.NOTIFY,
|
||||
data=ArrayOfEvents(
|
||||
[
|
||||
Event(
|
||||
key=event_key,
|
||||
raw_key=event_key.value,
|
||||
timestamp=0,
|
||||
level="",
|
||||
handling="",
|
||||
value=ProgramKey.DISHCARE_DISHWASHER_AUTO_1,
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify default values
|
||||
# Every time the program is updated, the available options should use the default value if existing
|
||||
for entity_id, available in zip(
|
||||
option_entity_id.values(), options_availability_stage_2, strict=True
|
||||
):
|
||||
assert hass.states.is_state(
|
||||
entity_id, STATE_OFF if available else STATE_UNAVAILABLE
|
||||
)
|
||||
for _, entity_id in (option_without_default, option_without_constraints):
|
||||
assert hass.states.is_state(entity_id, STATE_UNKNOWN)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"set_active_program_option_side_effect",
|
||||
"set_selected_program_option_side_effect",
|
||||
),
|
||||
[
|
||||
(
|
||||
ActiveProgramNotSetError("error.key"),
|
||||
SelectedProgramNotSetError("error.key"),
|
||||
),
|
||||
(
|
||||
HomeConnectError(),
|
||||
None,
|
||||
),
|
||||
(
|
||||
ActiveProgramNotSetError("error.key"),
|
||||
HomeConnectError(),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_option_entity_functionality_exception(
|
||||
set_active_program_option_side_effect: HomeConnectError | None,
|
||||
set_selected_program_option_side_effect: HomeConnectError | None,
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test that the option entity handles exceptions correctly."""
|
||||
entity_id = "switch.washer_i_dos_1_active"
|
||||
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
OptionKey.LAUNDRY_CARE_WASHER_I_DOS_1_ACTIVE,
|
||||
"Boolean",
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
assert hass.states.get(entity_id)
|
||||
|
||||
if set_active_program_option_side_effect:
|
||||
client.set_active_program_option = AsyncMock(
|
||||
side_effect=set_active_program_option_side_effect
|
||||
)
|
||||
if set_selected_program_option_side_effect:
|
||||
client.set_selected_program_option = AsyncMock(
|
||||
side_effect=set_selected_program_option_side_effect
|
||||
)
|
||||
|
||||
with pytest.raises(HomeAssistantError, match=r"Error.*setting.*option.*"):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
@ -7,17 +7,34 @@ from unittest.mock import AsyncMock, MagicMock
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfSettings,
|
||||
Event,
|
||||
EventKey,
|
||||
EventMessage,
|
||||
EventType,
|
||||
GetSetting,
|
||||
OptionKey,
|
||||
ProgramDefinition,
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
from aiohomeconnect.model.error import (
|
||||
ActiveProgramNotSetError,
|
||||
HomeConnectApiError,
|
||||
HomeConnectError,
|
||||
SelectedProgramNotSetError,
|
||||
)
|
||||
from aiohomeconnect.model.program import (
|
||||
ProgramDefinitionConstraints,
|
||||
ProgramDefinitionOption,
|
||||
)
|
||||
from aiohomeconnect.model.setting import SettingConstraints
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.home_connect.const import DOMAIN
|
||||
from homeassistant.components.number import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_STEP,
|
||||
ATTR_VALUE as SERVICE_ATTR_VALUE,
|
||||
DEFAULT_MAX_VALUE,
|
||||
DEFAULT_MIN_VALUE,
|
||||
@ -51,7 +68,6 @@ async def test_number(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
@ -63,6 +79,17 @@ async def test_paired_depaired_devices_flow(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
OptionKey.BSH_COMMON_FINISH_IN_RELATIVE,
|
||||
"Integer",
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
@ -369,3 +396,135 @@ async def test_number_entity_error(
|
||||
blocking=True,
|
||||
)
|
||||
assert getattr(client_with_exception, mock_attr).call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"set_active_program_options_side_effect",
|
||||
"set_selected_program_options_side_effect",
|
||||
"called_mock_method",
|
||||
),
|
||||
[
|
||||
(
|
||||
None,
|
||||
SelectedProgramNotSetError("error.key"),
|
||||
"set_active_program_option",
|
||||
),
|
||||
(
|
||||
ActiveProgramNotSetError("error.key"),
|
||||
None,
|
||||
"set_selected_program_option",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("appliance_ha_id", "entity_id", "option_key", "min", "max", "step_size", "unit"),
|
||||
[
|
||||
(
|
||||
"Oven",
|
||||
"number.oven_setpoint_temperature",
|
||||
OptionKey.COOKING_OVEN_SETPOINT_TEMPERATURE,
|
||||
50,
|
||||
260,
|
||||
1,
|
||||
"°C",
|
||||
),
|
||||
],
|
||||
indirect=["appliance_ha_id"],
|
||||
)
|
||||
async def test_options_functionality(
|
||||
entity_id: str,
|
||||
option_key: OptionKey,
|
||||
appliance_ha_id: str,
|
||||
min: int,
|
||||
max: int,
|
||||
step_size: int,
|
||||
unit: str,
|
||||
set_active_program_options_side_effect: ActiveProgramNotSetError | None,
|
||||
set_selected_program_options_side_effect: SelectedProgramNotSetError | None,
|
||||
called_mock_method: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test options functionality."""
|
||||
|
||||
async def set_program_option_side_effect(ha_id: str, *_, **kwargs) -> None:
|
||||
event_key = EventKey(kwargs["option_key"])
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
ha_id,
|
||||
EventType.NOTIFY,
|
||||
ArrayOfEvents(
|
||||
[
|
||||
Event(
|
||||
key=event_key,
|
||||
raw_key=event_key.value,
|
||||
timestamp=0,
|
||||
level="",
|
||||
handling="",
|
||||
value=kwargs["value"],
|
||||
unit=unit,
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
called_mock = AsyncMock(side_effect=set_program_option_side_effect)
|
||||
if set_active_program_options_side_effect:
|
||||
client.set_active_program_option.side_effect = (
|
||||
set_active_program_options_side_effect
|
||||
)
|
||||
else:
|
||||
assert set_selected_program_options_side_effect
|
||||
client.set_selected_program_option.side_effect = (
|
||||
set_selected_program_options_side_effect
|
||||
)
|
||||
setattr(client, called_mock_method, called_mock)
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
option_key,
|
||||
"Double",
|
||||
unit=unit,
|
||||
constraints=ProgramDefinitionConstraints(
|
||||
min=min,
|
||||
max=max,
|
||||
step_size=step_size,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
entity_state = hass.states.get(entity_id)
|
||||
assert entity_state
|
||||
assert entity_state.attributes["unit_of_measurement"] == unit
|
||||
assert entity_state.attributes[ATTR_MIN] == min
|
||||
assert entity_state.attributes[ATTR_MAX] == max
|
||||
assert entity_state.attributes[ATTR_STEP] == step_size
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, SERVICE_ATTR_VALUE: 80},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called_mock.called
|
||||
assert called_mock.call_args.args == (appliance_ha_id,)
|
||||
assert called_mock.call_args.kwargs == {
|
||||
"option_key": option_key,
|
||||
"value": 80,
|
||||
}
|
||||
assert hass.states.is_state(entity_id, "80.0")
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Tests for home_connect select entities."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
@ -10,13 +10,21 @@ from aiohomeconnect.model import (
|
||||
EventKey,
|
||||
EventMessage,
|
||||
EventType,
|
||||
OptionKey,
|
||||
ProgramDefinition,
|
||||
ProgramKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.error import (
|
||||
ActiveProgramNotSetError,
|
||||
HomeConnectError,
|
||||
SelectedProgramNotSetError,
|
||||
)
|
||||
from aiohomeconnect.model.program import (
|
||||
EnumerateProgram,
|
||||
EnumerateProgramConstraints,
|
||||
Execution,
|
||||
ProgramDefinitionConstraints,
|
||||
ProgramDefinitionOption,
|
||||
)
|
||||
import pytest
|
||||
|
||||
@ -70,6 +78,17 @@ async def test_paired_depaired_devices_flow(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE,
|
||||
"Enumeration",
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
@ -413,3 +432,132 @@ async def test_select_exception_handling(
|
||||
blocking=True,
|
||||
)
|
||||
assert getattr(client_with_exception, mock_attr).call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"set_active_program_options_side_effect",
|
||||
"set_selected_program_options_side_effect",
|
||||
"called_mock_method",
|
||||
),
|
||||
[
|
||||
(
|
||||
None,
|
||||
SelectedProgramNotSetError("error.key"),
|
||||
"set_active_program_option",
|
||||
),
|
||||
(
|
||||
ActiveProgramNotSetError("error.key"),
|
||||
None,
|
||||
"set_selected_program_option",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "option_key", "allowed_values", "expected_options"),
|
||||
[
|
||||
(
|
||||
"select.washer_temperature",
|
||||
OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE,
|
||||
None,
|
||||
{
|
||||
"laundry_care_washer_enum_type_temperature_cold",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_20",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_30",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_40",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_50",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_60",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_70",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_80",
|
||||
"laundry_care_washer_enum_type_temperature_g_c_90",
|
||||
"laundry_care_washer_enum_type_temperature_ul_cold",
|
||||
"laundry_care_washer_enum_type_temperature_ul_warm",
|
||||
"laundry_care_washer_enum_type_temperature_ul_hot",
|
||||
"laundry_care_washer_enum_type_temperature_ul_extra_hot",
|
||||
},
|
||||
),
|
||||
(
|
||||
"select.washer_temperature",
|
||||
OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE,
|
||||
[
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlCold",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlWarm",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlHot",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlExtraHot",
|
||||
],
|
||||
{
|
||||
"laundry_care_washer_enum_type_temperature_ul_cold",
|
||||
"laundry_care_washer_enum_type_temperature_ul_warm",
|
||||
"laundry_care_washer_enum_type_temperature_ul_hot",
|
||||
"laundry_care_washer_enum_type_temperature_ul_extra_hot",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_options_functionality(
|
||||
entity_id: str,
|
||||
option_key: OptionKey,
|
||||
allowed_values: list[str | None] | None,
|
||||
expected_options: set[str],
|
||||
appliance_ha_id: str,
|
||||
set_active_program_options_side_effect: ActiveProgramNotSetError | None,
|
||||
set_selected_program_options_side_effect: SelectedProgramNotSetError | None,
|
||||
called_mock_method: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test options functionality."""
|
||||
if set_active_program_options_side_effect:
|
||||
client.set_active_program_option.side_effect = (
|
||||
set_active_program_options_side_effect
|
||||
)
|
||||
else:
|
||||
assert set_selected_program_options_side_effect
|
||||
client.set_selected_program_option.side_effect = (
|
||||
set_selected_program_options_side_effect
|
||||
)
|
||||
called_mock: AsyncMock = getattr(client, called_mock_method)
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
option_key,
|
||||
"Enumeration",
|
||||
constraints=ProgramDefinitionConstraints(
|
||||
allowed_values=allowed_values
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
entity_state = hass.states.get(entity_id)
|
||||
assert entity_state
|
||||
assert set(entity_state.attributes[ATTR_OPTIONS]) == expected_options
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_OPTION: "laundry_care_washer_enum_type_temperature_ul_warm",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called_mock.called
|
||||
assert called_mock.call_args.args == (appliance_ha_id,)
|
||||
assert called_mock.call_args.kwargs == {
|
||||
"option_key": option_key,
|
||||
"value": "LaundryCare.Washer.EnumType.Temperature.UlWarm",
|
||||
}
|
||||
assert hass.states.is_state(
|
||||
entity_id, "laundry_care_washer_enum_type_temperature_ul_warm"
|
||||
)
|
||||
|
@ -5,17 +5,26 @@ from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfPrograms,
|
||||
ArrayOfSettings,
|
||||
Event,
|
||||
EventKey,
|
||||
EventMessage,
|
||||
EventType,
|
||||
GetSetting,
|
||||
OptionKey,
|
||||
ProgramDefinition,
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
from aiohomeconnect.model.event import ArrayOfEvents, EventType
|
||||
from aiohomeconnect.model.program import ArrayOfPrograms, EnumerateProgram
|
||||
from aiohomeconnect.model.error import (
|
||||
ActiveProgramNotSetError,
|
||||
HomeConnectApiError,
|
||||
HomeConnectError,
|
||||
SelectedProgramNotSetError,
|
||||
)
|
||||
from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption
|
||||
from aiohomeconnect.model.setting import SettingConstraints
|
||||
import pytest
|
||||
|
||||
@ -81,6 +90,17 @@ async def test_paired_depaired_devices_flow(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN,
|
||||
options=[
|
||||
ProgramDefinitionOption(
|
||||
OptionKey.LAUNDRY_CARE_WASHER_I_DOS_1_ACTIVE,
|
||||
"Boolean",
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
@ -840,3 +860,95 @@ async def test_create_issue(
|
||||
# Assert the issue is no longer present
|
||||
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"set_active_program_options_side_effect",
|
||||
"set_selected_program_options_side_effect",
|
||||
"called_mock_method",
|
||||
),
|
||||
[
|
||||
(
|
||||
None,
|
||||
SelectedProgramNotSetError("error.key"),
|
||||
"set_active_program_option",
|
||||
),
|
||||
(
|
||||
ActiveProgramNotSetError("error.key"),
|
||||
None,
|
||||
"set_selected_program_option",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "option_key", "appliance_ha_id"),
|
||||
[
|
||||
(
|
||||
"switch.dishwasher_half_load",
|
||||
OptionKey.DISHCARE_DISHWASHER_HALF_LOAD,
|
||||
"Dishwasher",
|
||||
)
|
||||
],
|
||||
indirect=["appliance_ha_id"],
|
||||
)
|
||||
async def test_options_functionality(
|
||||
entity_id: str,
|
||||
option_key: OptionKey,
|
||||
appliance_ha_id: str,
|
||||
set_active_program_options_side_effect: ActiveProgramNotSetError | None,
|
||||
set_selected_program_options_side_effect: SelectedProgramNotSetError | None,
|
||||
called_mock_method: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test options functionality."""
|
||||
if set_active_program_options_side_effect:
|
||||
client.set_active_program_option.side_effect = (
|
||||
set_active_program_options_side_effect
|
||||
)
|
||||
else:
|
||||
assert set_selected_program_options_side_effect
|
||||
client.set_selected_program_option.side_effect = (
|
||||
set_selected_program_options_side_effect
|
||||
)
|
||||
called_mock: AsyncMock = getattr(client, called_mock_method)
|
||||
client.get_available_program = AsyncMock(
|
||||
return_value=ProgramDefinition(
|
||||
ProgramKey.UNKNOWN, options=[ProgramDefinitionOption(option_key, "Boolean")]
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
assert hass.states.get(entity_id)
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called_mock.called
|
||||
assert called_mock.call_args.args == (appliance_ha_id,)
|
||||
assert called_mock.call_args.kwargs == {
|
||||
"option_key": option_key,
|
||||
"value": False,
|
||||
}
|
||||
assert hass.states.is_state(entity_id, STATE_OFF)
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called_mock.called
|
||||
assert called_mock.call_args.args == (appliance_ha_id,)
|
||||
assert called_mock.call_args.kwargs == {
|
||||
"option_key": option_key,
|
||||
"value": True,
|
||||
}
|
||||
assert hass.states.is_state(entity_id, STATE_ON)
|
||||
|
Loading…
x
Reference in New Issue
Block a user