diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 7213a2ebca0..1aa082f8c6c 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -1008,11 +1008,22 @@ async def async_service_temperature_set( ) hass = entity.hass - kwargs = {} + kwargs: dict[str, Any] = {} min_temp = entity.min_temp max_temp = entity.max_temp temp_unit = entity.temperature_unit + if ( + (target_low_temp := service_call.data.get(ATTR_TARGET_TEMP_LOW)) + and (target_high_temp := service_call.data.get(ATTR_TARGET_TEMP_HIGH)) + and target_low_temp > target_high_temp + ): + # Ensure target_low_temp is not higher than target_high_temp. + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="low_temp_higher_than_high_temp", + ) + for value, temp in service_call.data.items(): if value in CONVERTIBLE_ATTRIBUTE: kwargs[value] = check_temp = TemperatureConverter.convert( diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 3ff8d325da5..fc0bdaf0d72 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -270,6 +270,9 @@ "temp_out_of_range": { "message": "Provided temperature {check_temp} is not valid. Accepted range is {min_temp} to {max_temp}." }, + "low_temp_higher_than_high_temp": { + "message": "Target temperature low can not be higher than Target temperature high." + }, "humidity_out_of_range": { "message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}." } diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 1c9144b40f7..2b09c2801df 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1224,3 +1224,66 @@ async def test_temperature_validation( state = hass.states.get("climate.test") assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 10 assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25 + + +async def test_target_temp_high_higher_than_low( + hass: HomeAssistant, register_test_integration: MockConfigEntry +) -> None: + """Test that target high is higher than target low.""" + + class MockClimateEntityTemp(MockClimateEntity): + """Mock climate class with mocked aux heater.""" + + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) + _attr_current_temperature = 15 + _attr_target_temperature = 15 + _attr_target_temperature_high = 18 + _attr_target_temperature_low = 10 + _attr_target_temperature_step = PRECISION_WHOLE + + def set_temperature(self, **kwargs: Any) -> None: + """Set new target temperature.""" + if ATTR_TEMPERATURE in kwargs: + self._attr_target_temperature = kwargs[ATTR_TEMPERATURE] + if ATTR_TARGET_TEMP_HIGH in kwargs: + self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH] + self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW] + + test_climate = MockClimateEntityTemp( + name="Test", + unique_id="unique_climate_test", + ) + + setup_test_component_platform( + hass, DOMAIN, entities=[test_climate], from_config_entry=True + ) + await hass.config_entries.async_setup(register_test_integration.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("climate.test") + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 15 + assert state.attributes.get(ATTR_MIN_TEMP) == 7 + assert state.attributes.get(ATTR_MAX_TEMP) == 35 + + with pytest.raises( + ServiceValidationError, + match="Target temperature low can not be higher than Target temperature high", + ) as exc: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test", + ATTR_TARGET_TEMP_HIGH: "15", + ATTR_TARGET_TEMP_LOW: "20", + }, + blocking=True, + ) + assert ( + str(exc.value) + == "Target temperature low can not be higher than Target temperature high" + ) + assert exc.value.translation_key == "low_temp_higher_than_high_temp"