From ddaf0219577be43b350276616e62827d0b651598 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 5 Jul 2025 14:43:17 +0000 Subject: [PATCH] Add target temp range to water_heater --- .../components/water_heater/__init__.py | 51 +++++++++++++++---- .../components/water_heater/services.yaml | 28 ++++++++++ .../components/water_heater/strings.json | 14 +++++ 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index f4eb2a57770..70101acf8fc 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -68,6 +68,7 @@ class WaterHeaterEntityFeature(IntFlag): OPERATION_MODE = 2 AWAY_MODE = 4 ON_OFF = 8 + TARGET_TEMPERATURE_RANGE = 16 ATTR_MAX_TEMP = "max_temp" @@ -80,17 +81,26 @@ ATTR_TARGET_TEMP_LOW = "target_temp_low" ATTR_TARGET_TEMP_STEP = "target_temp_step" ATTR_CURRENT_TEMPERATURE = "current_temperature" -CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE] +CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW] _LOGGER = logging.getLogger(__name__) SET_AWAY_MODE_SCHEMA: VolDictType = { vol.Required(ATTR_AWAY_MODE): cv.boolean, } -SET_TEMPERATURE_SCHEMA: VolDictType = { - vol.Required(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), - vol.Optional(ATTR_OPERATION_MODE): cv.string, -} +SET_TEMPERATURE_SCHEMA = vol.All( + cv.has_at_least_one_key( + ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW + ), + cv.make_entity_service_schema( + { + vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), + vol.Optional(ATTR_OPERATION_MODE): cv.string, + } + ), +) SET_OPERATION_MODE_SCHEMA: VolDictType = { vol.Required(ATTR_OPERATION_MODE): cv.string, } @@ -121,7 +131,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, async_service_temperature_set, - [WaterHeaterEntityFeature.TARGET_TEMPERATURE], + [ + WaterHeaterEntityFeature.TARGET_TEMPERATURE, + WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE, + ], ) component.async_register_entity_service( SERVICE_SET_OPERATION_MODE, @@ -426,13 +439,31 @@ async def async_service_away_mode( async def async_service_temperature_set( - entity: WaterHeaterEntity, service: ServiceCall + entity: WaterHeaterEntity, service_call: ServiceCall ) -> None: """Handle set temperature service.""" - hass = entity.hass - kwargs = {} + if ( + ATTR_TEMPERATURE in service_call.data + and not entity.supported_features & WaterHeaterEntityFeature.TARGET_TEMPERATURE + ): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="missing_target_temperature_entity_feature", + ) + if ( + ATTR_TARGET_TEMP_LOW in service_call.data + and not entity.supported_features + & WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE + ): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="missing_target_temperature_range_entity_feature", + ) - for value, temp in service.data.items(): + hass = entity.hass + kwargs: dict[str, Any] = {} + + for value, temp in service_call.data.items(): if value in CONVERTIBLE_ATTRIBUTE: kwargs[value] = TemperatureConverter.convert( temp, hass.config.units.temperature_unit, entity.temperature_unit diff --git a/homeassistant/components/water_heater/services.yaml b/homeassistant/components/water_heater/services.yaml index b60cfdd8c48..e183c98e25d 100644 --- a/homeassistant/components/water_heater/services.yaml +++ b/homeassistant/components/water_heater/services.yaml @@ -14,8 +14,14 @@ set_temperature: target: entity: domain: water_heater + supported_features: + - water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE + - water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE fields: temperature: + filter: + supported_features: + - water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE required: true selector: number: @@ -23,6 +29,28 @@ set_temperature: max: 100 step: 0.5 unit_of_measurement: "°" + target_temp_high: + filter: + supported_features: + - water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE + advanced: true + selector: + number: + min: 0 + max: 250 + step: 0.1 + mode: box + target_temp_low: + filter: + supported_features: + - water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE + advanced: true + selector: + number: + min: 0 + max: 250 + step: 0.1 + mode: box operation_mode: example: eco selector: diff --git a/homeassistant/components/water_heater/strings.json b/homeassistant/components/water_heater/strings.json index 9cc3a84c3cd..ea1dd85fe9d 100644 --- a/homeassistant/components/water_heater/strings.json +++ b/homeassistant/components/water_heater/strings.json @@ -66,6 +66,14 @@ "name": "Temperature", "description": "New target temperature for the water heater." }, + "target_temp_high": { + "name": "Upper target temperature", + "description": "The max temperature setpoint." + }, + "target_temp_low": { + "name": "Lower target temperature", + "description": "The min temperature setpoint." + }, "operation_mode": { "name": "Operation mode", "description": "New value of the operation mode. For a list of possible modes, refer to the integration documentation." @@ -97,6 +105,12 @@ }, "operation_list_not_defined": { "message": "Operation mode {operation_mode} is not valid for {entity_id}. The operation list is not defined." + }, + "missing_target_temperature_entity_feature": { + "message": "Set temperature action was used with the target temperature parameter but the entity does not support it." + }, + "missing_target_temperature_range_entity_feature": { + "message": "Set temperature action was used with the target temperature low/high parameter but the entity does not support it." } } }