mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 03:37:07 +00:00
Add Home Connect action with recognized programs and options (#130662)
* Added recognized options to Home Connect actions * Fix ruff * Fix strings.json * Fix dishwasher typo * Improved test_bsh_key_transformations * Add missing return types * Added descriptions * Remove custom options * Fixes * Merge the 4 services (select, start, set options for active or selected program) And deprecate the original ones * Delete stale snapshots * Clean up logic after service validation * Make deprecated actions issues fixable And delete issue on entry unload * Fixes and improvements Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Improvements Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Fix name and descriptions * Add `affects_to` to strings and service.yaml * Add missing periods at strings * Fix Co-authored-by: Norbert Rittel <norbert@rittel.de> * Add tests to check if the flow removes the deprecated action issue --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Norbert Rittel <norbert@rittel.de>
This commit is contained in:
parent
d99044572a
commit
2bfe96dded
@ -2,11 +2,20 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from aiohomeconnect.client import Client as HomeConnectClient
|
||||
from aiohomeconnect.model import CommandKey, Option, OptionKey, ProgramKey, SettingKey
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfOptions,
|
||||
CommandKey,
|
||||
Option,
|
||||
OptionKey,
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
import voluptuous as vol
|
||||
|
||||
@ -19,34 +28,84 @@ from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import (
|
||||
AFFECTS_TO_ACTIVE_PROGRAM,
|
||||
AFFECTS_TO_SELECTED_PROGRAM,
|
||||
ATTR_AFFECTS_TO,
|
||||
ATTR_KEY,
|
||||
ATTR_PROGRAM,
|
||||
ATTR_UNIT,
|
||||
ATTR_VALUE,
|
||||
DOMAIN,
|
||||
OLD_NEW_UNIQUE_ID_SUFFIX_MAP,
|
||||
PROGRAM_ENUM_OPTIONS,
|
||||
SERVICE_OPTION_ACTIVE,
|
||||
SERVICE_OPTION_SELECTED,
|
||||
SERVICE_PAUSE_PROGRAM,
|
||||
SERVICE_RESUME_PROGRAM,
|
||||
SERVICE_SELECT_PROGRAM,
|
||||
SERVICE_SET_PROGRAM_AND_OPTIONS,
|
||||
SERVICE_SETTING,
|
||||
SERVICE_START_PROGRAM,
|
||||
SVE_TRANSLATION_PLACEHOLDER_KEY,
|
||||
SVE_TRANSLATION_PLACEHOLDER_PROGRAM,
|
||||
SVE_TRANSLATION_PLACEHOLDER_VALUE,
|
||||
TRANSLATION_KEYS_PROGRAMS_MAP,
|
||||
)
|
||||
from .coordinator import HomeConnectConfigEntry, HomeConnectCoordinator
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
from .utils import bsh_key_to_translation_key, get_dict_from_home_connect_error
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
|
||||
PROGRAM_OPTIONS = {
|
||||
bsh_key_to_translation_key(key): (
|
||||
key,
|
||||
value,
|
||||
)
|
||||
for key, value in {
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FILL_QUANTITY: int,
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_MULTIPLE_BEVERAGES: bool,
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_COFFEE_MILK_RATIO: int,
|
||||
OptionKey.DISHCARE_DISHWASHER_INTENSIV_ZONE: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_BRILLIANCE_DRY: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_VARIO_SPEED_PLUS: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_SILENCE_ON_DEMAND: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_HALF_LOAD: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_EXTRA_DRY: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_HYGIENE_PLUS: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_ECO_DRY: bool,
|
||||
OptionKey.DISHCARE_DISHWASHER_ZEOLITE_DRY: bool,
|
||||
OptionKey.COOKING_OVEN_SETPOINT_TEMPERATURE: int,
|
||||
OptionKey.COOKING_OVEN_FAST_PRE_HEAT: bool,
|
||||
OptionKey.LAUNDRY_CARE_WASHER_I_DOS_1_ACTIVE: bool,
|
||||
OptionKey.LAUNDRY_CARE_WASHER_I_DOS_2_ACTIVE: bool,
|
||||
}.items()
|
||||
}
|
||||
|
||||
TIME_PROGRAM_OPTIONS = {
|
||||
bsh_key_to_translation_key(key): (
|
||||
key,
|
||||
value,
|
||||
)
|
||||
for key, value in {
|
||||
OptionKey.BSH_COMMON_START_IN_RELATIVE: cv.time_period_str,
|
||||
OptionKey.BSH_COMMON_DURATION: cv.time_period_str,
|
||||
OptionKey.BSH_COMMON_FINISH_IN_RELATIVE: cv.time_period_str,
|
||||
}.items()
|
||||
}
|
||||
|
||||
|
||||
SERVICE_SETTING_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
@ -58,6 +117,7 @@ SERVICE_SETTING_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# DEPRECATED: Remove in 2025.9.0
|
||||
SERVICE_OPTION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
@ -70,6 +130,7 @@ SERVICE_OPTION_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# DEPRECATED: Remove in 2025.9.0
|
||||
SERVICE_PROGRAM_SCHEMA = vol.Any(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
@ -93,6 +154,51 @@ SERVICE_PROGRAM_SCHEMA = vol.Any(
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _require_program_or_at_least_one_option(data: dict) -> dict:
|
||||
if ATTR_PROGRAM not in data and not any(
|
||||
option_key in data
|
||||
for option_key in (
|
||||
PROGRAM_ENUM_OPTIONS | PROGRAM_OPTIONS | TIME_PROGRAM_OPTIONS
|
||||
)
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="required_program_or_one_option_at_least",
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
SERVICE_PROGRAM_AND_OPTIONS_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_DEVICE_ID): str,
|
||||
vol.Required(ATTR_AFFECTS_TO): vol.In(
|
||||
[AFFECTS_TO_ACTIVE_PROGRAM, AFFECTS_TO_SELECTED_PROGRAM]
|
||||
),
|
||||
vol.Optional(ATTR_PROGRAM): vol.In(TRANSLATION_KEYS_PROGRAMS_MAP.keys()),
|
||||
}
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
vol.Optional(translation_key): vol.In(allowed_values.keys())
|
||||
for translation_key, (
|
||||
key,
|
||||
allowed_values,
|
||||
) in PROGRAM_ENUM_OPTIONS.items()
|
||||
}
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
vol.Optional(translation_key): schema
|
||||
for translation_key, (key, schema) in (
|
||||
PROGRAM_OPTIONS | TIME_PROGRAM_OPTIONS
|
||||
).items()
|
||||
}
|
||||
),
|
||||
_require_program_or_at_least_one_option,
|
||||
)
|
||||
|
||||
SERVICE_COMMAND_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_ID): str})
|
||||
|
||||
PLATFORMS = [
|
||||
@ -144,7 +250,7 @@ async def _get_client_and_ha_id(
|
||||
return entry.runtime_data.client, ha_id
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: C901
|
||||
"""Set up Home Connect component."""
|
||||
|
||||
async def _async_service_program(call: ServiceCall, start: bool):
|
||||
@ -165,6 +271,57 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
else None
|
||||
)
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_set_program_and_option_actions",
|
||||
breaks_in_ha_version="2025.9.0",
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_set_program_and_option_actions",
|
||||
translation_placeholders={
|
||||
"new_action_key": SERVICE_SET_PROGRAM_AND_OPTIONS,
|
||||
"remove_release": "2025.9.0",
|
||||
"deprecated_action_yaml": "\n".join(
|
||||
[
|
||||
"```yaml",
|
||||
f"action: {DOMAIN}.{SERVICE_START_PROGRAM if start else SERVICE_SELECT_PROGRAM}",
|
||||
"data:",
|
||||
f" {ATTR_DEVICE_ID}: DEVICE_ID",
|
||||
f" {ATTR_PROGRAM}: {program}",
|
||||
*([f" {ATTR_KEY}: {options[0].key}"] if options else []),
|
||||
*([f" {ATTR_VALUE}: {options[0].value}"] if options else []),
|
||||
*(
|
||||
[f" {ATTR_UNIT}: {options[0].unit}"]
|
||||
if options and options[0].unit
|
||||
else []
|
||||
),
|
||||
"```",
|
||||
]
|
||||
),
|
||||
"new_action_yaml": "\n ".join(
|
||||
[
|
||||
"```yaml",
|
||||
f"action: {DOMAIN}.{SERVICE_SET_PROGRAM_AND_OPTIONS}",
|
||||
"data:",
|
||||
f" {ATTR_DEVICE_ID}: DEVICE_ID",
|
||||
f" {ATTR_AFFECTS_TO}: {AFFECTS_TO_ACTIVE_PROGRAM if start else AFFECTS_TO_SELECTED_PROGRAM}",
|
||||
f" {ATTR_PROGRAM}: {bsh_key_to_translation_key(program.value)}",
|
||||
*(
|
||||
[
|
||||
f" {bsh_key_to_translation_key(options[0].key)}: {options[0].value}"
|
||||
]
|
||||
if options
|
||||
else []
|
||||
),
|
||||
"```",
|
||||
]
|
||||
),
|
||||
"repo_link": "[aiohomeconnect](https://github.com/MartinHjelmare/aiohomeconnect)",
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
if start:
|
||||
await client.start_program(ha_id, program_key=program, options=options)
|
||||
@ -189,6 +346,44 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
unit = call.data.get(ATTR_UNIT)
|
||||
client, ha_id = await _get_client_and_ha_id(hass, call.data[ATTR_DEVICE_ID])
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_set_program_and_option_actions",
|
||||
breaks_in_ha_version="2025.9.0",
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_set_program_and_option_actions",
|
||||
translation_placeholders={
|
||||
"new_action_key": SERVICE_SET_PROGRAM_AND_OPTIONS,
|
||||
"remove_release": "2025.9.0",
|
||||
"deprecated_action_yaml": "\n".join(
|
||||
[
|
||||
"```yaml",
|
||||
f"action: {DOMAIN}.{SERVICE_OPTION_ACTIVE if active else SERVICE_OPTION_SELECTED}",
|
||||
"data:",
|
||||
f" {ATTR_DEVICE_ID}: DEVICE_ID",
|
||||
f" {ATTR_KEY}: {option_key}",
|
||||
f" {ATTR_VALUE}: {value}",
|
||||
*([f" {ATTR_UNIT}: {unit}"] if unit else []),
|
||||
"```",
|
||||
]
|
||||
),
|
||||
"new_action_yaml": "\n ".join(
|
||||
[
|
||||
"```yaml",
|
||||
f"action: {DOMAIN}.{SERVICE_SET_PROGRAM_AND_OPTIONS}",
|
||||
"data:",
|
||||
f" {ATTR_DEVICE_ID}: DEVICE_ID",
|
||||
f" {ATTR_AFFECTS_TO}: {AFFECTS_TO_ACTIVE_PROGRAM if active else AFFECTS_TO_SELECTED_PROGRAM}",
|
||||
f" {bsh_key_to_translation_key(option_key)}: {value}",
|
||||
"```",
|
||||
]
|
||||
),
|
||||
"repo_link": "[aiohomeconnect](https://github.com/MartinHjelmare/aiohomeconnect)",
|
||||
},
|
||||
)
|
||||
try:
|
||||
if active:
|
||||
await client.set_active_program_option(
|
||||
@ -272,6 +467,82 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Service for selecting a program."""
|
||||
await _async_service_program(call, False)
|
||||
|
||||
async def async_service_set_program_and_options(call: ServiceCall):
|
||||
"""Service for setting a program and options."""
|
||||
data = dict(call.data)
|
||||
program = data.pop(ATTR_PROGRAM, None)
|
||||
affects_to = data.pop(ATTR_AFFECTS_TO)
|
||||
client, ha_id = await _get_client_and_ha_id(hass, data.pop(ATTR_DEVICE_ID))
|
||||
|
||||
options: list[Option] = []
|
||||
|
||||
for option, value in data.items():
|
||||
if option in PROGRAM_ENUM_OPTIONS:
|
||||
options.append(
|
||||
Option(
|
||||
PROGRAM_ENUM_OPTIONS[option][0],
|
||||
PROGRAM_ENUM_OPTIONS[option][1][value],
|
||||
)
|
||||
)
|
||||
elif option in PROGRAM_OPTIONS:
|
||||
option_key = PROGRAM_OPTIONS[option][0]
|
||||
options.append(Option(option_key, value))
|
||||
elif option in TIME_PROGRAM_OPTIONS:
|
||||
options.append(
|
||||
Option(
|
||||
TIME_PROGRAM_OPTIONS[option][0],
|
||||
int(cast(timedelta, value).total_seconds()),
|
||||
)
|
||||
)
|
||||
method_call: Awaitable[Any]
|
||||
exception_translation_key: str
|
||||
if program:
|
||||
program = (
|
||||
program
|
||||
if isinstance(program, ProgramKey)
|
||||
else TRANSLATION_KEYS_PROGRAMS_MAP[program]
|
||||
)
|
||||
|
||||
if affects_to == AFFECTS_TO_ACTIVE_PROGRAM:
|
||||
method_call = client.start_program(
|
||||
ha_id, program_key=program, options=options
|
||||
)
|
||||
exception_translation_key = "start_program"
|
||||
elif affects_to == AFFECTS_TO_SELECTED_PROGRAM:
|
||||
method_call = client.set_selected_program(
|
||||
ha_id, program_key=program, options=options
|
||||
)
|
||||
exception_translation_key = "select_program"
|
||||
else:
|
||||
array_of_options = ArrayOfOptions(options)
|
||||
if affects_to == AFFECTS_TO_ACTIVE_PROGRAM:
|
||||
method_call = client.set_active_program_options(
|
||||
ha_id, array_of_options=array_of_options
|
||||
)
|
||||
exception_translation_key = "set_options_active_program"
|
||||
else:
|
||||
# affects_to is AFFECTS_TO_SELECTED_PROGRAM
|
||||
method_call = client.set_selected_program_options(
|
||||
ha_id, array_of_options=array_of_options
|
||||
)
|
||||
exception_translation_key = "set_options_selected_program"
|
||||
|
||||
try:
|
||||
await method_call
|
||||
except HomeConnectError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=exception_translation_key,
|
||||
translation_placeholders={
|
||||
**get_dict_from_home_connect_error(err),
|
||||
**(
|
||||
{SVE_TRANSLATION_PLACEHOLDER_PROGRAM: program}
|
||||
if program
|
||||
else {}
|
||||
),
|
||||
},
|
||||
) from err
|
||||
|
||||
async def async_service_start_program(call: ServiceCall):
|
||||
"""Service for starting a program."""
|
||||
await _async_service_program(call, True)
|
||||
@ -315,6 +586,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async_service_start_program,
|
||||
schema=SERVICE_PROGRAM_SCHEMA,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_SET_PROGRAM_AND_OPTIONS,
|
||||
async_service_set_program_and_options,
|
||||
schema=SERVICE_PROGRAM_AND_OPTIONS_SCHEMA,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -349,6 +626,7 @@ async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: HomeConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
async_delete_issue(hass, DOMAIN, "deprecated_set_program_and_option_actions")
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
"""Constants for the Home Connect integration."""
|
||||
|
||||
from aiohomeconnect.model import EventKey, SettingKey, StatusKey
|
||||
from typing import cast
|
||||
|
||||
from aiohomeconnect.model import EventKey, OptionKey, ProgramKey, SettingKey, StatusKey
|
||||
|
||||
from .utils import bsh_key_to_translation_key
|
||||
|
||||
DOMAIN = "home_connect"
|
||||
|
||||
@ -52,15 +56,18 @@ SERVICE_OPTION_SELECTED = "set_option_selected"
|
||||
SERVICE_PAUSE_PROGRAM = "pause_program"
|
||||
SERVICE_RESUME_PROGRAM = "resume_program"
|
||||
SERVICE_SELECT_PROGRAM = "select_program"
|
||||
SERVICE_SET_PROGRAM_AND_OPTIONS = "set_program_and_options"
|
||||
SERVICE_SETTING = "change_setting"
|
||||
SERVICE_START_PROGRAM = "start_program"
|
||||
|
||||
|
||||
ATTR_AFFECTS_TO = "affects_to"
|
||||
ATTR_KEY = "key"
|
||||
ATTR_PROGRAM = "program"
|
||||
ATTR_UNIT = "unit"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
AFFECTS_TO_ACTIVE_PROGRAM = "active_program"
|
||||
AFFECTS_TO_SELECTED_PROGRAM = "selected_program"
|
||||
|
||||
SVE_TRANSLATION_KEY_SET_SETTING = "set_setting_entity"
|
||||
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME = "appliance_name"
|
||||
@ -70,6 +77,244 @@ SVE_TRANSLATION_PLACEHOLDER_KEY = "key"
|
||||
SVE_TRANSLATION_PLACEHOLDER_VALUE = "value"
|
||||
|
||||
|
||||
TRANSLATION_KEYS_PROGRAMS_MAP = {
|
||||
bsh_key_to_translation_key(program.value): cast(ProgramKey, program)
|
||||
for program in ProgramKey
|
||||
if program != ProgramKey.UNKNOWN
|
||||
}
|
||||
|
||||
PROGRAMS_TRANSLATION_KEYS_MAP = {
|
||||
value: key for key, value in TRANSLATION_KEYS_PROGRAMS_MAP.items()
|
||||
}
|
||||
|
||||
REFERENCE_MAP_ID_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CleaningRobot.EnumType.AvailableMaps.TempMap",
|
||||
"ConsumerProducts.CleaningRobot.EnumType.AvailableMaps.Map1",
|
||||
"ConsumerProducts.CleaningRobot.EnumType.AvailableMaps.Map2",
|
||||
"ConsumerProducts.CleaningRobot.EnumType.AvailableMaps.Map3",
|
||||
)
|
||||
}
|
||||
|
||||
CLEANING_MODE_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CleaningRobot.EnumType.CleaningModes.Silent",
|
||||
"ConsumerProducts.CleaningRobot.EnumType.CleaningModes.Standard",
|
||||
"ConsumerProducts.CleaningRobot.EnumType.CleaningModes.Power",
|
||||
)
|
||||
}
|
||||
|
||||
BEAN_AMOUNT_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.VeryMild",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.Mild",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.MildPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.Normal",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.NormalPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.Strong",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.StrongPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.VeryStrong",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.VeryStrongPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.ExtraStrong",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.DoubleShot",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.DoubleShotPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.DoubleShotPlusPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.TripleShot",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.TripleShotPlus",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.CoffeeGround",
|
||||
)
|
||||
}
|
||||
|
||||
COFFEE_TEMPERATURE_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.88C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.90C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.92C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.94C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.95C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.CoffeeTemperature.96C",
|
||||
)
|
||||
}
|
||||
|
||||
BEAN_CONTAINER_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanContainerSelection.Right",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.BeanContainerSelection.Left",
|
||||
)
|
||||
}
|
||||
|
||||
FLOW_RATE_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.FlowRate.Normal",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.FlowRate.Intense",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.FlowRate.IntensePlus",
|
||||
)
|
||||
}
|
||||
|
||||
HOT_WATER_TEMPERATURE_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.WhiteTea",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.GreenTea",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.BlackTea",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.50C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.55C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.60C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.65C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.70C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.75C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.80C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.85C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.90C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.95C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.97C",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.122F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.131F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.140F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.149F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.158F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.167F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.176F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.185F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.194F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.203F",
|
||||
"ConsumerProducts.CoffeeMaker.EnumType.HotWaterTemperature.Max",
|
||||
)
|
||||
}
|
||||
|
||||
DRYING_TARGET_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"LaundryCare.Dryer.EnumType.DryingTarget.IronDry",
|
||||
"LaundryCare.Dryer.EnumType.DryingTarget.GentleDry",
|
||||
"LaundryCare.Dryer.EnumType.DryingTarget.CupboardDry",
|
||||
"LaundryCare.Dryer.EnumType.DryingTarget.CupboardDryPlus",
|
||||
"LaundryCare.Dryer.EnumType.DryingTarget.ExtraDry",
|
||||
)
|
||||
}
|
||||
|
||||
VENTING_LEVEL_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"Cooking.Hood.EnumType.Stage.FanOff",
|
||||
"Cooking.Hood.EnumType.Stage.FanStage01",
|
||||
"Cooking.Hood.EnumType.Stage.FanStage02",
|
||||
"Cooking.Hood.EnumType.Stage.FanStage03",
|
||||
"Cooking.Hood.EnumType.Stage.FanStage04",
|
||||
"Cooking.Hood.EnumType.Stage.FanStage05",
|
||||
)
|
||||
}
|
||||
|
||||
INTENSIVE_LEVEL_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"Cooking.Hood.EnumType.IntensiveStage.IntensiveStageOff",
|
||||
"Cooking.Hood.EnumType.IntensiveStage.IntensiveStage1",
|
||||
"Cooking.Hood.EnumType.IntensiveStage.IntensiveStage2",
|
||||
)
|
||||
}
|
||||
|
||||
WARMING_LEVEL_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"Cooking.Oven.EnumType.WarmingLevel.Low",
|
||||
"Cooking.Oven.EnumType.WarmingLevel.Medium",
|
||||
"Cooking.Oven.EnumType.WarmingLevel.High",
|
||||
)
|
||||
}
|
||||
|
||||
TEMPERATURE_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"LaundryCare.Washer.EnumType.Temperature.Cold",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC20",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC30",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC40",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC50",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC60",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC70",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC80",
|
||||
"LaundryCare.Washer.EnumType.Temperature.GC90",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlCold",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlWarm",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlHot",
|
||||
"LaundryCare.Washer.EnumType.Temperature.UlExtraHot",
|
||||
)
|
||||
}
|
||||
|
||||
SPIN_SPEED_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.Off",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1400",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1600",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.UlOff",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.UlLow",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.UlMedium",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.UlHigh",
|
||||
)
|
||||
}
|
||||
|
||||
VARIO_PERFECT_OPTIONS = {
|
||||
bsh_key_to_translation_key(option): option
|
||||
for option in (
|
||||
"LaundryCare.Common.EnumType.VarioPerfect.Off",
|
||||
"LaundryCare.Common.EnumType.VarioPerfect.EcoPerfect",
|
||||
"LaundryCare.Common.EnumType.VarioPerfect.SpeedPerfect",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
PROGRAM_ENUM_OPTIONS = {
|
||||
bsh_key_to_translation_key(option_key): (
|
||||
option_key,
|
||||
options,
|
||||
)
|
||||
for option_key, options in (
|
||||
(
|
||||
OptionKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_REFERENCE_MAP_ID,
|
||||
REFERENCE_MAP_ID_OPTIONS,
|
||||
),
|
||||
(
|
||||
OptionKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_CLEANING_MODE,
|
||||
CLEANING_MODE_OPTIONS,
|
||||
),
|
||||
(OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEAN_AMOUNT, BEAN_AMOUNT_OPTIONS),
|
||||
(
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_COFFEE_TEMPERATURE,
|
||||
COFFEE_TEMPERATURE_OPTIONS,
|
||||
),
|
||||
(
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEAN_CONTAINER_SELECTION,
|
||||
BEAN_CONTAINER_OPTIONS,
|
||||
),
|
||||
(OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FLOW_RATE, FLOW_RATE_OPTIONS),
|
||||
(
|
||||
OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_HOT_WATER_TEMPERATURE,
|
||||
HOT_WATER_TEMPERATURE_OPTIONS,
|
||||
),
|
||||
(OptionKey.LAUNDRY_CARE_DRYER_DRYING_TARGET, DRYING_TARGET_OPTIONS),
|
||||
(OptionKey.COOKING_COMMON_HOOD_VENTING_LEVEL, VENTING_LEVEL_OPTIONS),
|
||||
(OptionKey.COOKING_COMMON_HOOD_INTENSIVE_LEVEL, INTENSIVE_LEVEL_OPTIONS),
|
||||
(OptionKey.COOKING_OVEN_WARMING_LEVEL, WARMING_LEVEL_OPTIONS),
|
||||
(OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE, TEMPERATURE_OPTIONS),
|
||||
(OptionKey.LAUNDRY_CARE_WASHER_SPIN_SPEED, SPIN_SPEED_OPTIONS),
|
||||
(OptionKey.LAUNDRY_CARE_COMMON_VARIO_PERFECT, VARIO_PERFECT_OPTIONS),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
OLD_NEW_UNIQUE_ID_SUFFIX_MAP = {
|
||||
"ChildLock": SettingKey.BSH_COMMON_CHILD_LOCK,
|
||||
"Operation State": StatusKey.BSH_COMMON_OPERATION_STATE,
|
||||
|
@ -18,6 +18,9 @@
|
||||
"set_option_selected": {
|
||||
"service": "mdi:gesture-tap"
|
||||
},
|
||||
"set_program_and_options": {
|
||||
"service": "mdi:form-select"
|
||||
},
|
||||
"change_setting": {
|
||||
"service": "mdi:cog"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Home Connect",
|
||||
"codeowners": ["@DavidMStraub", "@Diegorro98", "@MartinHjelmare"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials"],
|
||||
"dependencies": ["application_credentials", "repairs"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiohomeconnect"],
|
||||
|
@ -15,24 +15,20 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import APPLIANCES_WITH_PROGRAMS, DOMAIN, SVE_TRANSLATION_PLACEHOLDER_PROGRAM
|
||||
from .const import (
|
||||
APPLIANCES_WITH_PROGRAMS,
|
||||
DOMAIN,
|
||||
PROGRAMS_TRANSLATION_KEYS_MAP,
|
||||
SVE_TRANSLATION_PLACEHOLDER_PROGRAM,
|
||||
TRANSLATION_KEYS_PROGRAMS_MAP,
|
||||
)
|
||||
from .coordinator import (
|
||||
HomeConnectApplianceData,
|
||||
HomeConnectConfigEntry,
|
||||
HomeConnectCoordinator,
|
||||
)
|
||||
from .entity import HomeConnectEntity
|
||||
from .utils import bsh_key_to_translation_key, get_dict_from_home_connect_error
|
||||
|
||||
TRANSLATION_KEYS_PROGRAMS_MAP = {
|
||||
bsh_key_to_translation_key(program.value): cast(ProgramKey, program)
|
||||
for program in ProgramKey
|
||||
if program != ProgramKey.UNKNOWN
|
||||
}
|
||||
|
||||
PROGRAMS_TRANSLATION_KEYS_MAP = {
|
||||
value: key for key, value in TRANSLATION_KEYS_PROGRAMS_MAP.items()
|
||||
}
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
|
@ -46,6 +46,532 @@ select_program:
|
||||
example: "seconds"
|
||||
selector:
|
||||
text:
|
||||
set_program_and_options:
|
||||
fields:
|
||||
device_id:
|
||||
required: true
|
||||
selector:
|
||||
device:
|
||||
integration: home_connect
|
||||
affects_to:
|
||||
example: active_program
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
translation_key: affects_to
|
||||
options:
|
||||
- active_program
|
||||
- selected_program
|
||||
program:
|
||||
example: dishcare_dishwasher_program_auto2
|
||||
required: true
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
custom_value: false
|
||||
translation_key: programs
|
||||
options:
|
||||
- consumer_products_cleaning_robot_program_cleaning_clean_all
|
||||
- consumer_products_cleaning_robot_program_cleaning_clean_map
|
||||
- consumer_products_cleaning_robot_program_basic_go_home
|
||||
- consumer_products_coffee_maker_program_beverage_ristretto
|
||||
- consumer_products_coffee_maker_program_beverage_espresso
|
||||
- consumer_products_coffee_maker_program_beverage_espresso_doppio
|
||||
- consumer_products_coffee_maker_program_beverage_coffee
|
||||
- consumer_products_coffee_maker_program_beverage_x_l_coffee
|
||||
- consumer_products_coffee_maker_program_beverage_caffe_grande
|
||||
- consumer_products_coffee_maker_program_beverage_espresso_macchiato
|
||||
- consumer_products_coffee_maker_program_beverage_cappuccino
|
||||
- consumer_products_coffee_maker_program_beverage_latte_macchiato
|
||||
- consumer_products_coffee_maker_program_beverage_caffe_latte
|
||||
- consumer_products_coffee_maker_program_beverage_milk_froth
|
||||
- consumer_products_coffee_maker_program_beverage_warm_milk
|
||||
- consumer_products_coffee_maker_program_coffee_world_kleiner_brauner
|
||||
- consumer_products_coffee_maker_program_coffee_world_grosser_brauner
|
||||
- consumer_products_coffee_maker_program_coffee_world_verlaengerter
|
||||
- consumer_products_coffee_maker_program_coffee_world_verlaengerter_braun
|
||||
- consumer_products_coffee_maker_program_coffee_world_wiener_melange
|
||||
- consumer_products_coffee_maker_program_coffee_world_flat_white
|
||||
- consumer_products_coffee_maker_program_coffee_world_cortado
|
||||
- consumer_products_coffee_maker_program_coffee_world_cafe_cortado
|
||||
- consumer_products_coffee_maker_program_coffee_world_cafe_con_leche
|
||||
- consumer_products_coffee_maker_program_coffee_world_cafe_au_lait
|
||||
- consumer_products_coffee_maker_program_coffee_world_doppio
|
||||
- consumer_products_coffee_maker_program_coffee_world_kaapi
|
||||
- consumer_products_coffee_maker_program_coffee_world_koffie_verkeerd
|
||||
- consumer_products_coffee_maker_program_coffee_world_galao
|
||||
- consumer_products_coffee_maker_program_coffee_world_garoto
|
||||
- consumer_products_coffee_maker_program_coffee_world_americano
|
||||
- consumer_products_coffee_maker_program_coffee_world_red_eye
|
||||
- consumer_products_coffee_maker_program_coffee_world_black_eye
|
||||
- consumer_products_coffee_maker_program_coffee_world_dead_eye
|
||||
- consumer_products_coffee_maker_program_beverage_hot_water
|
||||
- dishcare_dishwasher_program_pre_rinse
|
||||
- dishcare_dishwasher_program_auto_1
|
||||
- dishcare_dishwasher_program_auto_2
|
||||
- dishcare_dishwasher_program_auto_3
|
||||
- dishcare_dishwasher_program_eco_50
|
||||
- dishcare_dishwasher_program_quick_45
|
||||
- dishcare_dishwasher_program_intensiv_70
|
||||
- dishcare_dishwasher_program_normal_65
|
||||
- dishcare_dishwasher_program_glas_40
|
||||
- dishcare_dishwasher_program_glass_care
|
||||
- dishcare_dishwasher_program_night_wash
|
||||
- dishcare_dishwasher_program_quick_65
|
||||
- dishcare_dishwasher_program_normal_45
|
||||
- dishcare_dishwasher_program_intensiv_45
|
||||
- dishcare_dishwasher_program_auto_half_load
|
||||
- dishcare_dishwasher_program_intensiv_power
|
||||
- dishcare_dishwasher_program_magic_daily
|
||||
- dishcare_dishwasher_program_super_60
|
||||
- dishcare_dishwasher_program_kurz_60
|
||||
- dishcare_dishwasher_program_express_sparkle_65
|
||||
- dishcare_dishwasher_program_machine_care
|
||||
- dishcare_dishwasher_program_steam_fresh
|
||||
- dishcare_dishwasher_program_maximum_cleaning
|
||||
- dishcare_dishwasher_program_mixed_load
|
||||
- laundry_care_dryer_program_cotton
|
||||
- laundry_care_dryer_program_synthetic
|
||||
- laundry_care_dryer_program_mix
|
||||
- laundry_care_dryer_program_blankets
|
||||
- laundry_care_dryer_program_business_shirts
|
||||
- laundry_care_dryer_program_down_feathers
|
||||
- laundry_care_dryer_program_hygiene
|
||||
- laundry_care_dryer_program_jeans
|
||||
- laundry_care_dryer_program_outdoor
|
||||
- laundry_care_dryer_program_synthetic_refresh
|
||||
- laundry_care_dryer_program_towels
|
||||
- laundry_care_dryer_program_delicates
|
||||
- laundry_care_dryer_program_super_40
|
||||
- laundry_care_dryer_program_shirts_15
|
||||
- laundry_care_dryer_program_pillow
|
||||
- laundry_care_dryer_program_anti_shrink
|
||||
- laundry_care_dryer_program_my_time_my_drying_time
|
||||
- laundry_care_dryer_program_time_cold
|
||||
- laundry_care_dryer_program_time_warm
|
||||
- laundry_care_dryer_program_in_basket
|
||||
- laundry_care_dryer_program_time_cold_fix_time_cold_20
|
||||
- laundry_care_dryer_program_time_cold_fix_time_cold_30
|
||||
- laundry_care_dryer_program_time_cold_fix_time_cold_60
|
||||
- laundry_care_dryer_program_time_warm_fix_time_warm_30
|
||||
- laundry_care_dryer_program_time_warm_fix_time_warm_40
|
||||
- laundry_care_dryer_program_time_warm_fix_time_warm_60
|
||||
- laundry_care_dryer_program_dessous
|
||||
- cooking_common_program_hood_automatic
|
||||
- cooking_common_program_hood_venting
|
||||
- cooking_common_program_hood_delayed_shut_off
|
||||
- cooking_oven_program_heating_mode_pre_heating
|
||||
- cooking_oven_program_heating_mode_hot_air
|
||||
- cooking_oven_program_heating_mode_hot_air_eco
|
||||
- cooking_oven_program_heating_mode_hot_air_grilling
|
||||
- cooking_oven_program_heating_mode_top_bottom_heating
|
||||
- cooking_oven_program_heating_mode_top_bottom_heating_eco
|
||||
- cooking_oven_program_heating_mode_bottom_heating
|
||||
- cooking_oven_program_heating_mode_pizza_setting
|
||||
- cooking_oven_program_heating_mode_slow_cook
|
||||
- cooking_oven_program_heating_mode_intensive_heat
|
||||
- cooking_oven_program_heating_mode_keep_warm
|
||||
- cooking_oven_program_heating_mode_preheat_ovenware
|
||||
- cooking_oven_program_heating_mode_frozen_heatup_special
|
||||
- cooking_oven_program_heating_mode_desiccation
|
||||
- cooking_oven_program_heating_mode_defrost
|
||||
- cooking_oven_program_heating_mode_proof
|
||||
- cooking_oven_program_heating_mode_hot_air_30_steam
|
||||
- cooking_oven_program_heating_mode_hot_air_60_steam
|
||||
- cooking_oven_program_heating_mode_hot_air_80_steam
|
||||
- cooking_oven_program_heating_mode_hot_air_100_steam
|
||||
- cooking_oven_program_heating_mode_sabbath_programme
|
||||
- cooking_oven_program_microwave_90_watt
|
||||
- cooking_oven_program_microwave_180_watt
|
||||
- cooking_oven_program_microwave_360_watt
|
||||
- cooking_oven_program_microwave_600_watt
|
||||
- cooking_oven_program_microwave_900_watt
|
||||
- cooking_oven_program_microwave_1000_watt
|
||||
- cooking_oven_program_microwave_max
|
||||
- cooking_oven_program_heating_mode_warming_drawer
|
||||
- laundry_care_washer_program_cotton
|
||||
- laundry_care_washer_program_cotton_cotton_eco
|
||||
- laundry_care_washer_program_cotton_eco_4060
|
||||
- laundry_care_washer_program_cotton_colour
|
||||
- laundry_care_washer_program_easy_care
|
||||
- laundry_care_washer_program_mix
|
||||
- laundry_care_washer_program_mix_night_wash
|
||||
- laundry_care_washer_program_delicates_silk
|
||||
- laundry_care_washer_program_wool
|
||||
- laundry_care_washer_program_sensitive
|
||||
- laundry_care_washer_program_auto_30
|
||||
- laundry_care_washer_program_auto_40
|
||||
- laundry_care_washer_program_auto_60
|
||||
- laundry_care_washer_program_chiffon
|
||||
- laundry_care_washer_program_curtains
|
||||
- laundry_care_washer_program_dark_wash
|
||||
- laundry_care_washer_program_dessous
|
||||
- laundry_care_washer_program_monsoon
|
||||
- laundry_care_washer_program_outdoor
|
||||
- laundry_care_washer_program_plush_toy
|
||||
- laundry_care_washer_program_shirts_blouses
|
||||
- laundry_care_washer_program_sport_fitness
|
||||
- laundry_care_washer_program_towels
|
||||
- laundry_care_washer_program_water_proof
|
||||
- laundry_care_washer_program_power_speed_59
|
||||
- laundry_care_washer_program_super_153045_super_15
|
||||
- laundry_care_washer_program_super_153045_super_1530
|
||||
- laundry_care_washer_program_down_duvet_duvet
|
||||
- laundry_care_washer_program_rinse_rinse_spin_drain
|
||||
- laundry_care_washer_program_drum_clean
|
||||
- laundry_care_washer_dryer_program_cotton
|
||||
- laundry_care_washer_dryer_program_cotton_eco_4060
|
||||
- laundry_care_washer_dryer_program_mix
|
||||
- laundry_care_washer_dryer_program_easy_care
|
||||
- laundry_care_washer_dryer_program_wash_and_dry_60
|
||||
- laundry_care_washer_dryer_program_wash_and_dry_90
|
||||
cleaning_robot_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
consumer_products_cleaning_robot_option_reference_map_id:
|
||||
example: consumer_products_cleaning_robot_enum_type_available_maps_map1
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: available_maps
|
||||
options:
|
||||
- consumer_products_cleaning_robot_enum_type_available_maps_temp_map
|
||||
- consumer_products_cleaning_robot_enum_type_available_maps_map1
|
||||
- consumer_products_cleaning_robot_enum_type_available_maps_map2
|
||||
- consumer_products_cleaning_robot_enum_type_available_maps_map3
|
||||
consumer_products_cleaning_robot_option_cleaning_mode:
|
||||
example: consumer_products_cleaning_robot_enum_type_cleaning_modes_standard
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: cleaning_mode
|
||||
options:
|
||||
- consumer_products_cleaning_robot_enum_type_cleaning_modes_silent
|
||||
- consumer_products_cleaning_robot_enum_type_cleaning_modes_standard
|
||||
- consumer_products_cleaning_robot_enum_type_cleaning_modes_power
|
||||
coffee_maker_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
consumer_products_coffee_maker_option_bean_amount:
|
||||
example: consumer_products_coffee_maker_enum_type_bean_amount_normal
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: bean_amount
|
||||
options:
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_very_mild
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_mild
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_mild_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_normal
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_normal_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_strong
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_strong_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_very_strong
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_very_strong_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_extra_strong
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_double_shot
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_double_shot_plus_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_triple_shot
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_triple_shot_plus
|
||||
- consumer_products_coffee_maker_enum_type_bean_amount_coffee_ground
|
||||
consumer_products_coffee_maker_option_fill_quantity:
|
||||
example: 60
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
step: 1
|
||||
mode: box
|
||||
unit_of_measurement: ml
|
||||
consumer_products_coffee_maker_option_coffee_temperature:
|
||||
example: consumer_products_coffee_maker_enum_type_coffee_temperature_88_c
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: coffee_temperature
|
||||
options:
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_88_c
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_90_c
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_92_c
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_94_c
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_95_c
|
||||
- consumer_products_coffee_maker_enum_type_coffee_temperature_96_c
|
||||
consumer_products_coffee_maker_option_bean_container:
|
||||
example: consumer_products_coffee_maker_enum_type_bean_container_selection_right
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: bean_container
|
||||
options:
|
||||
- consumer_products_coffee_maker_enum_type_bean_container_selection_right
|
||||
- consumer_products_coffee_maker_enum_type_bean_container_selection_left
|
||||
consumer_products_coffee_maker_option_flow_rate:
|
||||
example: consumer_products_coffee_maker_enum_type_flow_rate_normal
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: flow_rate
|
||||
options:
|
||||
- consumer_products_coffee_maker_enum_type_flow_rate_normal
|
||||
- consumer_products_coffee_maker_enum_type_flow_rate_intense
|
||||
- consumer_products_coffee_maker_enum_type_flow_rate_intense_plus
|
||||
consumer_products_coffee_maker_option_multiple_beverages:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
consumer_products_coffee_maker_option_coffee_milk_ratio:
|
||||
example: 50
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
unit_of_measurement: "%"
|
||||
step: 10
|
||||
min: 10
|
||||
max: 90
|
||||
consumer_products_coffee_maker_option_hot_water_temperature:
|
||||
example: consumer_products_coffee_maker_enum_type_hot_water_temperature_50_c
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: 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
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_black_tea
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_50_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_55_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_60_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_65_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_70_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_75_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_80_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_85_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_90_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_95_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_97_c
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_122_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_131_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_140_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_149_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_158_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_167_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_176_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_185_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_194_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_203_f
|
||||
- consumer_products_coffee_maker_enum_type_hot_water_temperature_max
|
||||
dish_washer_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
b_s_h_common_option_start_in_relative:
|
||||
example: "30:00"
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
dishcare_dishwasher_option_intensiv_zone:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_brilliance_dry:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_vario_speed_plus:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_silence_on_demand:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_half_load:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_extra_dry:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_hygiene_plus:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_eco_dry:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dishcare_dishwasher_option_zeolite_dry:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
dryer_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
laundry_care_dryer_option_drying_target:
|
||||
example: laundry_care_dryer_enum_type_drying_target_iron_dry
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: drying_target
|
||||
options:
|
||||
- laundry_care_dryer_enum_type_drying_target_iron_dry
|
||||
- laundry_care_dryer_enum_type_drying_target_gentle_dry
|
||||
- laundry_care_dryer_enum_type_drying_target_cupboard_dry
|
||||
- laundry_care_dryer_enum_type_drying_target_cupboard_dry_plus
|
||||
- laundry_care_dryer_enum_type_drying_target_extra_dry
|
||||
hood_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
cooking_hood_option_venting_level:
|
||||
example: cooking_hood_enum_type_stage_fan_stage01
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: venting_level
|
||||
options:
|
||||
- cooking_hood_enum_type_stage_fan_off
|
||||
- cooking_hood_enum_type_stage_fan_stage01
|
||||
- cooking_hood_enum_type_stage_fan_stage02
|
||||
- cooking_hood_enum_type_stage_fan_stage03
|
||||
- cooking_hood_enum_type_stage_fan_stage04
|
||||
- cooking_hood_enum_type_stage_fan_stage05
|
||||
cooking_hood_option_intensive_level:
|
||||
example: cooking_hood_enum_type_intensive_stage_intensive_stage1
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: intensive_level
|
||||
options:
|
||||
- cooking_hood_enum_type_intensive_stage_intensive_stage_off
|
||||
- cooking_hood_enum_type_intensive_stage_intensive_stage1
|
||||
- cooking_hood_enum_type_intensive_stage_intensive_stage2
|
||||
oven_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
cooking_oven_option_setpoint_temperature:
|
||||
example: 180
|
||||
required: false
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
step: 1
|
||||
mode: box
|
||||
unit_of_measurement: °C/°F
|
||||
b_s_h_common_option_duration:
|
||||
example: "30:00"
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
cooking_oven_option_fast_pre_heat:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
warming_drawer_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
cooking_oven_option_warming_level:
|
||||
example: cooking_oven_enum_type_warming_level_medium
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: warming_level
|
||||
options:
|
||||
- cooking_oven_enum_type_warming_level_low
|
||||
- cooking_oven_enum_type_warming_level_medium
|
||||
- cooking_oven_enum_type_warming_level_high
|
||||
washer_options:
|
||||
collapsed: true
|
||||
fields:
|
||||
laundry_care_washer_option_temperature:
|
||||
example: laundry_care_washer_enum_type_temperature_g_c40
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: washer_temperature
|
||||
options:
|
||||
- laundry_care_washer_enum_type_temperature_cold
|
||||
- laundry_care_washer_enum_type_temperature_g_c20
|
||||
- laundry_care_washer_enum_type_temperature_g_c30
|
||||
- laundry_care_washer_enum_type_temperature_g_c40
|
||||
- laundry_care_washer_enum_type_temperature_g_c50
|
||||
- laundry_care_washer_enum_type_temperature_g_c60
|
||||
- laundry_care_washer_enum_type_temperature_g_c70
|
||||
- laundry_care_washer_enum_type_temperature_g_c80
|
||||
- laundry_care_washer_enum_type_temperature_g_c90
|
||||
- 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
|
||||
laundry_care_washer_option_spin_speed:
|
||||
example: laundry_care_washer_enum_type_spin_speed_r_p_m800
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: spin_speed
|
||||
options:
|
||||
- laundry_care_washer_enum_type_spin_speed_off
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m400
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m600
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m800
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1000
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1200
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1400
|
||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1600
|
||||
- laundry_care_washer_enum_type_spin_speed_ul_off
|
||||
- laundry_care_washer_enum_type_spin_speed_ul_low
|
||||
- laundry_care_washer_enum_type_spin_speed_ul_medium
|
||||
- laundry_care_washer_enum_type_spin_speed_ul_high
|
||||
b_s_h_common_option_finish_in_relative:
|
||||
example: "30:00"
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
laundry_care_washer_option_i_dos1_active:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
laundry_care_washer_option_i_dos2_active:
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
laundry_care_washer_option_vario_perfect:
|
||||
example: laundry_care_common_enum_type_vario_perfect_eco_perfect
|
||||
required: false
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
translation_key: vario_perfect
|
||||
options:
|
||||
- laundry_care_common_enum_type_vario_perfect_off
|
||||
- laundry_care_common_enum_type_vario_perfect_eco_perfect
|
||||
- laundry_care_common_enum_type_vario_perfect_speed_perfect
|
||||
pause_program:
|
||||
fields:
|
||||
device_id:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ from aiohomeconnect.client import Client as HomeConnectClient
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfHomeAppliances,
|
||||
ArrayOfOptions,
|
||||
ArrayOfPrograms,
|
||||
ArrayOfSettings,
|
||||
ArrayOfStatus,
|
||||
@ -199,13 +200,13 @@ def _get_set_program_side_effect(
|
||||
return set_program_side_effect
|
||||
|
||||
|
||||
def _get_set_key_value_side_effect(
|
||||
event_queue: asyncio.Queue[list[EventMessage]], parameter_key: str
|
||||
def _get_set_setting_side_effect(
|
||||
event_queue: asyncio.Queue[list[EventMessage]],
|
||||
):
|
||||
"""Set program options side effect."""
|
||||
"""Set settings side effect."""
|
||||
|
||||
async def set_key_value_side_effect(ha_id: str, *_, **kwargs) -> None:
|
||||
event_key = EventKey(kwargs[parameter_key])
|
||||
async def set_settings_side_effect(ha_id: str, *_, **kwargs) -> None:
|
||||
event_key = EventKey(kwargs["setting_key"])
|
||||
await event_queue.put(
|
||||
[
|
||||
EventMessage(
|
||||
@ -227,7 +228,48 @@ def _get_set_key_value_side_effect(
|
||||
]
|
||||
)
|
||||
|
||||
return set_key_value_side_effect
|
||||
return set_settings_side_effect
|
||||
|
||||
|
||||
def _get_set_program_options_side_effect(
|
||||
event_queue: asyncio.Queue[list[EventMessage]],
|
||||
):
|
||||
"""Set programs side effect."""
|
||||
|
||||
async def set_program_options_side_effect(ha_id: str, *_, **kwargs) -> None:
|
||||
await event_queue.put(
|
||||
[
|
||||
EventMessage(
|
||||
ha_id,
|
||||
EventType.NOTIFY,
|
||||
ArrayOfEvents(
|
||||
[
|
||||
Event(
|
||||
key=EventKey(option.key),
|
||||
raw_key=option.key.value,
|
||||
timestamp=0,
|
||||
level="",
|
||||
handling="",
|
||||
value=option.value,
|
||||
)
|
||||
for option in (
|
||||
cast(ArrayOfOptions, kwargs["array_of_options"]).options
|
||||
if "array_of_options" in kwargs
|
||||
else [
|
||||
Option(
|
||||
kwargs["option_key"],
|
||||
kwargs["value"],
|
||||
unit=kwargs["unit"],
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
return set_program_options_side_effect
|
||||
|
||||
|
||||
async def _get_all_programs_side_effect(ha_id: str) -> ArrayOfPrograms:
|
||||
@ -319,13 +361,19 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
),
|
||||
)
|
||||
mock.set_active_program_option = AsyncMock(
|
||||
side_effect=_get_set_key_value_side_effect(event_queue, "option_key"),
|
||||
side_effect=_get_set_program_options_side_effect(event_queue),
|
||||
)
|
||||
mock.set_active_program_options = AsyncMock(
|
||||
side_effect=_get_set_program_options_side_effect(event_queue),
|
||||
)
|
||||
mock.set_selected_program_option = AsyncMock(
|
||||
side_effect=_get_set_key_value_side_effect(event_queue, "option_key"),
|
||||
side_effect=_get_set_program_options_side_effect(event_queue),
|
||||
)
|
||||
mock.set_selected_program_options = AsyncMock(
|
||||
side_effect=_get_set_program_options_side_effect(event_queue),
|
||||
)
|
||||
mock.set_setting = AsyncMock(
|
||||
side_effect=_get_set_key_value_side_effect(event_queue, "setting_key"),
|
||||
side_effect=_get_set_setting_side_effect(event_queue),
|
||||
)
|
||||
mock.get_settings = AsyncMock(side_effect=_get_settings_side_effect)
|
||||
mock.get_setting = AsyncMock(side_effect=_get_setting_side_effect)
|
||||
@ -363,7 +411,9 @@ def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
||||
mock.stop_program = AsyncMock(side_effect=exception)
|
||||
mock.set_selected_program = AsyncMock(side_effect=exception)
|
||||
mock.set_active_program_option = AsyncMock(side_effect=exception)
|
||||
mock.set_active_program_options = AsyncMock(side_effect=exception)
|
||||
mock.set_selected_program_option = AsyncMock(side_effect=exception)
|
||||
mock.set_selected_program_options = AsyncMock(side_effect=exception)
|
||||
mock.set_setting = AsyncMock(side_effect=exception)
|
||||
mock.get_settings = AsyncMock(side_effect=exception)
|
||||
mock.get_setting = AsyncMock(side_effect=exception)
|
||||
|
79
tests/components/home_connect/snapshots/test_init.ambr
Normal file
79
tests/components/home_connect/snapshots/test_init.ambr
Normal file
@ -0,0 +1,79 @@
|
||||
# serializer version: 1
|
||||
# name: test_set_program_and_options[service_call0-set_selected_program]
|
||||
_Call(
|
||||
tuple(
|
||||
'SIEMENS-HCS03WCH1-7BC6383CF794',
|
||||
),
|
||||
dict({
|
||||
'options': list([
|
||||
dict({
|
||||
'display_value': None,
|
||||
'key': <OptionKey.BSH_COMMON_START_IN_RELATIVE: 'BSH.Common.Option.StartInRelative'>,
|
||||
'name': None,
|
||||
'unit': None,
|
||||
'value': 1800,
|
||||
}),
|
||||
]),
|
||||
'program_key': <ProgramKey.DISHCARE_DISHWASHER_ECO_50: 'Dishcare.Dishwasher.Program.Eco50'>,
|
||||
}),
|
||||
)
|
||||
# ---
|
||||
# name: test_set_program_and_options[service_call1-start_program]
|
||||
_Call(
|
||||
tuple(
|
||||
'SIEMENS-HCS03WCH1-7BC6383CF794',
|
||||
),
|
||||
dict({
|
||||
'options': list([
|
||||
dict({
|
||||
'display_value': None,
|
||||
'key': <OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEAN_AMOUNT: 'ConsumerProducts.CoffeeMaker.Option.BeanAmount'>,
|
||||
'name': None,
|
||||
'unit': None,
|
||||
'value': 'ConsumerProducts.CoffeeMaker.EnumType.BeanAmount.Normal',
|
||||
}),
|
||||
]),
|
||||
'program_key': <ProgramKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COFFEE: 'ConsumerProducts.CoffeeMaker.Program.Beverage.Coffee'>,
|
||||
}),
|
||||
)
|
||||
# ---
|
||||
# name: test_set_program_and_options[service_call2-set_active_program_options]
|
||||
_Call(
|
||||
tuple(
|
||||
'SIEMENS-HCS03WCH1-7BC6383CF794',
|
||||
),
|
||||
dict({
|
||||
'array_of_options': dict({
|
||||
'options': list([
|
||||
dict({
|
||||
'display_value': None,
|
||||
'key': <OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_COFFEE_MILK_RATIO: 'ConsumerProducts.CoffeeMaker.Option.CoffeeMilkRatio'>,
|
||||
'name': None,
|
||||
'unit': None,
|
||||
'value': 60,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
# ---
|
||||
# name: test_set_program_and_options[service_call3-set_selected_program_options]
|
||||
_Call(
|
||||
tuple(
|
||||
'SIEMENS-HCS03WCH1-7BC6383CF794',
|
||||
),
|
||||
dict({
|
||||
'array_of_options': dict({
|
||||
'options': list([
|
||||
dict({
|
||||
'display_value': None,
|
||||
'key': <OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FILL_QUANTITY: 'ConsumerProducts.CoffeeMaker.Option.FillQuantity'>,
|
||||
'name': None,
|
||||
'unit': None,
|
||||
'value': 35,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
# ---
|
@ -1,6 +1,7 @@
|
||||
"""Test the integration init functionality."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -10,6 +11,7 @@ from aiohomeconnect.model.error import HomeConnectError, UnauthorizedError
|
||||
import pytest
|
||||
import requests_mock
|
||||
import respx
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.home_connect.const import DOMAIN
|
||||
@ -22,6 +24,7 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
from script.hassfest.translations import RE_TRANSLATION_KEY
|
||||
|
||||
from .conftest import (
|
||||
@ -34,8 +37,9 @@ from .conftest import (
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
SERVICE_KV_CALL_PARAMS = [
|
||||
DEPRECATED_SERVICE_KV_CALL_PARAMS = [
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "set_option_active",
|
||||
@ -57,6 +61,10 @@ SERVICE_KV_CALL_PARAMS = [
|
||||
},
|
||||
"blocking": True,
|
||||
},
|
||||
]
|
||||
|
||||
SERVICE_KV_CALL_PARAMS = [
|
||||
*DEPRECATED_SERVICE_KV_CALL_PARAMS,
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "change_setting",
|
||||
@ -125,6 +133,62 @@ SERVICE_APPLIANCE_METHOD_MAPPING = {
|
||||
"start_program": "start_program",
|
||||
}
|
||||
|
||||
SERVICE_VALIDATION_ERROR_MAPPING = {
|
||||
"set_option_active": r"Error.*setting.*options.*active.*program.*",
|
||||
"set_option_selected": r"Error.*setting.*options.*selected.*program.*",
|
||||
"change_setting": r"Error.*assigning.*value.*setting.*",
|
||||
"pause_program": r"Error.*executing.*command.*",
|
||||
"resume_program": r"Error.*executing.*command.*",
|
||||
"select_program": r"Error.*selecting.*program.*",
|
||||
"start_program": r"Error.*starting.*program.*",
|
||||
}
|
||||
|
||||
|
||||
SERVICES_SET_PROGRAM_AND_OPTIONS = [
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "set_program_and_options",
|
||||
"service_data": {
|
||||
"device_id": "DEVICE_ID",
|
||||
"affects_to": "selected_program",
|
||||
"program": "dishcare_dishwasher_program_eco_50",
|
||||
"b_s_h_common_option_start_in_relative": "00:30:00",
|
||||
},
|
||||
"blocking": True,
|
||||
},
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "set_program_and_options",
|
||||
"service_data": {
|
||||
"device_id": "DEVICE_ID",
|
||||
"affects_to": "active_program",
|
||||
"program": "consumer_products_coffee_maker_program_beverage_coffee",
|
||||
"consumer_products_coffee_maker_option_bean_amount": "consumer_products_coffee_maker_enum_type_bean_amount_normal",
|
||||
},
|
||||
"blocking": True,
|
||||
},
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "set_program_and_options",
|
||||
"service_data": {
|
||||
"device_id": "DEVICE_ID",
|
||||
"affects_to": "active_program",
|
||||
"consumer_products_coffee_maker_option_coffee_milk_ratio": 60,
|
||||
},
|
||||
"blocking": True,
|
||||
},
|
||||
{
|
||||
"domain": DOMAIN,
|
||||
"service": "set_program_and_options",
|
||||
"service_data": {
|
||||
"device_id": "DEVICE_ID",
|
||||
"affects_to": "selected_program",
|
||||
"consumer_products_coffee_maker_option_fill_quantity": 35,
|
||||
},
|
||||
"blocking": True,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def test_entry_setup(
|
||||
hass: HomeAssistant,
|
||||
@ -244,7 +308,7 @@ async def test_client_error(
|
||||
"service_call",
|
||||
SERVICE_KV_CALL_PARAMS + SERVICE_COMMAND_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
||||
)
|
||||
async def test_services(
|
||||
async def test_key_value_services(
|
||||
service_call: dict[str, Any],
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
@ -273,11 +337,188 @@ async def test_services(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"service_call",
|
||||
DEPRECATED_SERVICE_KV_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
||||
)
|
||||
async def test_programs_and_options_actions_deprecation(
|
||||
service_call: dict[str, Any],
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test deprecated service keys."""
|
||||
issue_id = "deprecated_set_program_and_option_actions"
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance_ha_id)},
|
||||
)
|
||||
|
||||
service_call["service_data"]["device_id"] = device_entry.id
|
||||
await hass.services.async_call(**service_call)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(issue_registry.issues) == 1
|
||||
issue = issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
assert issue
|
||||
|
||||
_client = await hass_client()
|
||||
resp = await _client.post(
|
||||
"/api/repairs/issues/fix",
|
||||
json={"handler": DOMAIN, "issue_id": issue.issue_id},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
flow_id = (await resp.json())["flow_id"]
|
||||
resp = await _client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||
|
||||
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
await hass.services.async_call(**service_call)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(issue_registry.issues) == 1
|
||||
assert issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# 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(
|
||||
("service_call", "called_method"),
|
||||
zip(
|
||||
SERVICES_SET_PROGRAM_AND_OPTIONS,
|
||||
[
|
||||
"set_selected_program",
|
||||
"start_program",
|
||||
"set_active_program_options",
|
||||
"set_selected_program_options",
|
||||
],
|
||||
strict=True,
|
||||
),
|
||||
)
|
||||
async def test_set_program_and_options(
|
||||
service_call: dict[str, Any],
|
||||
called_method: str,
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test recognized options."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance_ha_id)},
|
||||
)
|
||||
|
||||
service_call["service_data"]["device_id"] = device_entry.id
|
||||
await hass.services.async_call(**service_call)
|
||||
await hass.async_block_till_done()
|
||||
method_mock: MagicMock = getattr(client, called_method)
|
||||
assert method_mock.call_count == 1
|
||||
assert method_mock.call_args == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_call", "error_regex"),
|
||||
zip(
|
||||
SERVICES_SET_PROGRAM_AND_OPTIONS,
|
||||
[
|
||||
r"Error.*selecting.*program.*",
|
||||
r"Error.*starting.*program.*",
|
||||
r"Error.*setting.*options.*active.*program.*",
|
||||
r"Error.*setting.*options.*selected.*program.*",
|
||||
],
|
||||
strict=True,
|
||||
),
|
||||
)
|
||||
async def test_set_program_and_options_exceptions(
|
||||
service_call: dict[str, Any],
|
||||
error_regex: str,
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client_with_exception: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test recognized options."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client_with_exception)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance_ha_id)},
|
||||
)
|
||||
|
||||
service_call["service_data"]["device_id"] = device_entry.id
|
||||
with pytest.raises(HomeAssistantError, match=error_regex):
|
||||
await hass.services.async_call(**service_call)
|
||||
|
||||
|
||||
async def test_required_program_or_at_least_an_option(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"Test that the set_program_and_options does raise an exception if no program nor options are set."
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance_ha_id)},
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ServiceValidationError,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"set_program_and_options",
|
||||
{
|
||||
"device_id": device_entry.id,
|
||||
"affects_to": "selected_program",
|
||||
},
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"service_call",
|
||||
SERVICE_KV_CALL_PARAMS + SERVICE_COMMAND_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
||||
)
|
||||
async def test_services_exception(
|
||||
async def test_services_exception_device_id(
|
||||
service_call: dict[str, Any],
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@ -348,6 +589,40 @@ async def test_services_appliance_not_found(
|
||||
await hass.services.async_call(**service_call)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"service_call",
|
||||
SERVICE_KV_CALL_PARAMS + SERVICE_COMMAND_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
||||
)
|
||||
async def test_services_exception(
|
||||
service_call: dict[str, Any],
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client_with_exception: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Raise a ValueError when device id does not match."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client_with_exception)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance_ha_id)},
|
||||
)
|
||||
|
||||
service_call["service_data"]["device_id"] = device_entry.id
|
||||
|
||||
service_name = service_call["service"]
|
||||
with pytest.raises(
|
||||
HomeAssistantError,
|
||||
match=SERVICE_VALIDATION_ERROR_MAPPING[service_name],
|
||||
):
|
||||
await hass.services.async_call(**service_call)
|
||||
|
||||
|
||||
async def test_entity_migration(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
Loading…
x
Reference in New Issue
Block a user