diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 40dc20d7d183..32e675464147 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -5,11 +5,13 @@ import functools as ft import logging from typing import Any, final, override -import probatio from propcache.api import cached_property from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( + +# ATTR_TEMPERATURE, SERVICE_TURN_ON and SERVICE_TURN_OFF are re-exported for +# integrations importing them from the water_heater component root. +from homeassistant.const import ( # noqa: F401 ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE, @@ -19,24 +21,29 @@ from homeassistant.const import ( STATE_ON, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.temperature import display_temp as show_temp -from homeassistant.helpers.typing import ConfigType, VolDictType -from homeassistant.util.hass_dict import HassKey +from homeassistant.helpers.typing import ConfigType from homeassistant.util.unit_conversion import TemperatureConverter -from .const import ( +from .const import ( # noqa: F401 + ATTR_AWAY_MODE, + ATTR_OPERATION_MODE, + DATA_COMPONENT, DOMAIN, + SERVICE_SET_AWAY_MODE, + SERVICE_SET_OPERATION_MODE, + SERVICE_SET_TEMPERATURE, WaterHeaterCapabilityAttribute, WaterHeaterEntityFeature, WaterHeaterStateAttribute, ) +from .services import async_setup_services -DATA_COMPONENT: HassKey[EntityComponent[WaterHeaterEntity]] = HassKey(DOMAIN) ENTITY_ID_FORMAT = DOMAIN + ".{}" PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE @@ -45,9 +52,6 @@ SCAN_INTERVAL = timedelta(seconds=60) DEFAULT_MIN_TEMP = 110 DEFAULT_MAX_TEMP = 140 -SERVICE_SET_AWAY_MODE = "set_away_mode" -SERVICE_SET_TEMPERATURE = "set_temperature" -SERVICE_SET_OPERATION_MODE = "set_operation_mode" STATE_ECO = "eco" STATE_ELECTRIC = "electric" @@ -59,28 +63,15 @@ STATE_GAS = "gas" ATTR_MAX_TEMP = "max_temp" ATTR_MIN_TEMP = "min_temp" -ATTR_AWAY_MODE = "away_mode" -ATTR_OPERATION_MODE = "operation_mode" ATTR_OPERATION_LIST = "operation_list" ATTR_TARGET_TEMP_HIGH = "target_temp_high" ATTR_TARGET_TEMP_LOW = "target_temp_low" ATTR_TARGET_TEMP_STEP = "target_temp_step" ATTR_CURRENT_TEMPERATURE = "current_temperature" -CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE] _LOGGER = logging.getLogger(__name__) -SET_AWAY_MODE_SCHEMA: VolDictType = { - probatio.Required(ATTR_AWAY_MODE): cv.boolean, -} -SET_TEMPERATURE_SCHEMA: VolDictType = { - probatio.Required(ATTR_TEMPERATURE, "temperature"): probatio.Coerce(float), - probatio.Optional(ATTR_OPERATION_MODE): cv.string, -} -SET_OPERATION_MODE_SCHEMA: VolDictType = { - probatio.Required(ATTR_OPERATION_MODE): cv.string, -} # mypy: disallow-any-generics @@ -92,30 +83,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) await component.async_setup(config) - component.async_register_entity_service( - SERVICE_TURN_ON, None, "async_turn_on", [WaterHeaterEntityFeature.ON_OFF] - ) - component.async_register_entity_service( - SERVICE_TURN_OFF, None, "async_turn_off", [WaterHeaterEntityFeature.ON_OFF] - ) - component.async_register_entity_service( - SERVICE_SET_AWAY_MODE, - SET_AWAY_MODE_SCHEMA, - async_service_away_mode, - [WaterHeaterEntityFeature.AWAY_MODE], - ) - component.async_register_entity_service( - SERVICE_SET_TEMPERATURE, - SET_TEMPERATURE_SCHEMA, - async_service_temperature_set, - [WaterHeaterEntityFeature.TARGET_TEMPERATURE], - ) - component.async_register_entity_service( - SERVICE_SET_OPERATION_MODE, - SET_OPERATION_MODE_SCHEMA, - "async_handle_set_operation_mode", - [WaterHeaterEntityFeature.OPERATION_MODE], - ) + async_setup_services(hass) return True @@ -401,31 +369,3 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def supported_features(self) -> WaterHeaterEntityFeature: """Return the list of supported features.""" return self._attr_supported_features - - -async def async_service_away_mode( - entity: WaterHeaterEntity, service: ServiceCall -) -> None: - """Handle away mode service.""" - if service.data[ATTR_AWAY_MODE]: - await entity.async_turn_away_mode_on() - else: - await entity.async_turn_away_mode_off() - - -async def async_service_temperature_set( - entity: WaterHeaterEntity, service: ServiceCall -) -> None: - """Handle set temperature service.""" - hass = entity.hass - kwargs = {} - - for value, temp in service.data.items(): - if value in CONVERTIBLE_ATTRIBUTE: - kwargs[value] = TemperatureConverter.convert( - temp, hass.config.units.temperature_unit, entity.temperature_unit - ) - else: - kwargs[value] = temp - - await entity.async_set_temperature(**kwargs) diff --git a/homeassistant/components/water_heater/const.py b/homeassistant/components/water_heater/const.py index 78dd9a00e2b9..fb894e5a0a06 100644 --- a/homeassistant/components/water_heater/const.py +++ b/homeassistant/components/water_heater/const.py @@ -1,12 +1,27 @@ """Support for water heater devices.""" from enum import IntFlag, StrEnum -from typing import Final +from typing import TYPE_CHECKING, Final from homeassistant.helpers.deprecation import EnumWithDeprecatedMembers +from homeassistant.util.hass_dict import HassKey + +if TYPE_CHECKING: + from homeassistant.helpers.entity_component import EntityComponent + + from . import WaterHeaterEntity DOMAIN: Final = "water_heater" +ATTR_AWAY_MODE: Final = "away_mode" +ATTR_OPERATION_MODE: Final = "operation_mode" + +SERVICE_SET_AWAY_MODE: Final = "set_away_mode" +SERVICE_SET_OPERATION_MODE: Final = "set_operation_mode" +SERVICE_SET_TEMPERATURE: Final = "set_temperature" + +DATA_COMPONENT: HassKey[EntityComponent[WaterHeaterEntity]] = HassKey(DOMAIN) + class WaterHeaterCapabilityAttribute(StrEnum): """Capability attributes for water heater devices.""" diff --git a/homeassistant/components/water_heater/services.py b/homeassistant/components/water_heater/services.py new file mode 100644 index 000000000000..d21d94227c7b --- /dev/null +++ b/homeassistant/components/water_heater/services.py @@ -0,0 +1,101 @@ +"""Services for the water_heater integration.""" + +from typing import TYPE_CHECKING + +import probatio + +from homeassistant.const import ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import VolDictType +from homeassistant.util.unit_conversion import TemperatureConverter + +from .const import ( + ATTR_AWAY_MODE, + ATTR_OPERATION_MODE, + DATA_COMPONENT, + SERVICE_SET_AWAY_MODE, + SERVICE_SET_OPERATION_MODE, + SERVICE_SET_TEMPERATURE, + WaterHeaterEntityFeature, +) + +if TYPE_CHECKING: + from . import WaterHeaterEntity + +CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE] + + +SET_AWAY_MODE_SCHEMA: VolDictType = { + probatio.Required(ATTR_AWAY_MODE): cv.boolean, +} + + +SET_TEMPERATURE_SCHEMA: VolDictType = { + probatio.Required(ATTR_TEMPERATURE, "temperature"): probatio.Coerce(float), + probatio.Optional(ATTR_OPERATION_MODE): cv.string, +} + + +SET_OPERATION_MODE_SCHEMA: VolDictType = { + probatio.Required(ATTR_OPERATION_MODE): cv.string, +} + + +async def _async_service_away_mode( + entity: WaterHeaterEntity, service: ServiceCall +) -> None: + """Handle away mode service.""" + if service.data[ATTR_AWAY_MODE]: + await entity.async_turn_away_mode_on() + else: + await entity.async_turn_away_mode_off() + + +async def _async_service_temperature_set( + entity: WaterHeaterEntity, service: ServiceCall +) -> None: + """Handle set temperature service.""" + hass = entity.hass + kwargs = {} + + for value, temp in service.data.items(): + if value in CONVERTIBLE_ATTRIBUTE: + kwargs[value] = TemperatureConverter.convert( + temp, hass.config.units.temperature_unit, entity.temperature_unit + ) + else: + kwargs[value] = temp + + await entity.async_set_temperature(**kwargs) + + +@callback +def async_setup_services(hass: HomeAssistant) -> None: + """Register the water_heater services.""" + component = hass.data[DATA_COMPONENT] + + component.async_register_entity_service( + SERVICE_TURN_ON, None, "async_turn_on", [WaterHeaterEntityFeature.ON_OFF] + ) + component.async_register_entity_service( + SERVICE_TURN_OFF, None, "async_turn_off", [WaterHeaterEntityFeature.ON_OFF] + ) + component.async_register_entity_service( + SERVICE_SET_AWAY_MODE, + SET_AWAY_MODE_SCHEMA, + _async_service_away_mode, + [WaterHeaterEntityFeature.AWAY_MODE], + ) + component.async_register_entity_service( + SERVICE_SET_TEMPERATURE, + SET_TEMPERATURE_SCHEMA, + _async_service_temperature_set, + [WaterHeaterEntityFeature.TARGET_TEMPERATURE], + ) + component.async_register_entity_service( + SERVICE_SET_OPERATION_MODE, + SET_OPERATION_MODE_SCHEMA, + "async_handle_set_operation_mode", + [WaterHeaterEntityFeature.OPERATION_MODE], + ) diff --git a/tests/components/water_heater/test_init.py b/tests/components/water_heater/test_init.py index 48d079981a0c..88ca1da1099b 100644 --- a/tests/components/water_heater/test_init.py +++ b/tests/components/water_heater/test_init.py @@ -9,10 +9,10 @@ import pytest from homeassistant.components.water_heater import ( DOMAIN, SERVICE_SET_OPERATION_MODE, - SET_TEMPERATURE_SCHEMA, WaterHeaterEntity, WaterHeaterEntityFeature, ) +from homeassistant.components.water_heater.services import SET_TEMPERATURE_SCHEMA from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform, UnitOfTemperature from homeassistant.core import HomeAssistant