From 12d037441bfebaba199f8fb7e8cb5b29da85fec1 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 14 Aug 2024 17:24:43 +0000 Subject: [PATCH] More fixes --- homeassistant/components/climate/__init__.py | 63 +++++++++++++++---- homeassistant/components/climate/strings.json | 6 ++ tests/components/climate/test_init.py | 57 ++++++++++++++++- 3 files changed, 114 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 97ed190ec17a5a..0e548eba882e1b 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -547,8 +547,37 @@ def state_attributes(self) -> dict[str, Any]: data[ATTR_TARGET_TEMP_LOW] = show_temp( hass, self.target_temperature_low, temperature_unit, precision ) - if hasattr(self, "min_temperature_range"): - data[ATTR_MIN_TEMP_RANGE] = self.min_temperature_range + if ( + hasattr(self, "min_temperature_range") + and (min_temp_range := self.min_temperature_range) is not None + ): + data[ATTR_MIN_TEMP_RANGE] = min_temp_range + if ( + self.hass.config.units.temperature_unit != temperature_unit + and temperature_unit == UnitOfTemperature.CELSIUS + and ( + show_temp_range := show_temp( + hass, + min_temp_range, + temperature_unit, + precision, + ) + ) + ): + data[ATTR_MIN_TEMP_RANGE] = show_temp_range - 32 + elif ( + self.hass.config.units.temperature_unit != temperature_unit + and temperature_unit == UnitOfTemperature.FAHRENHEIT + and ( + show_temp_range := show_temp( + hass, + min_temp_range, + temperature_unit, + precision, + ) + ) + ): + data[ATTR_MIN_TEMP_RANGE] = show_temp_range + 32 if (current_humidity := self.current_humidity) is not None: data[ATTR_CURRENT_HUMIDITY] = current_humidity @@ -961,9 +990,12 @@ async def async_service_temperature_set( else: kwargs[value] = temp + if not hasattr(entity, "min_temperature_range"): + await entity.async_set_temperature(**kwargs) + return + if ( - hasattr(entity, "min_temperature_range") - and (min_temp_range := entity.min_temperature_range) + (min_temp_range := entity.min_temperature_range) and (target_low_temp := kwargs.get(ATTR_TARGET_TEMP_LOW)) and (target_high_temp := kwargs.get(ATTR_TARGET_TEMP_HIGH)) and (target_high_temp - target_low_temp) < min_temp_range @@ -973,12 +1005,10 @@ async def async_service_temperature_set( raise ServiceValidationError( translation_domain=DOMAIN, translation_key="low_temp_higher_than_high_temp", - translation_placeholders={ - "min_temp": str(target_low_temp), - "max_temp": str(target_high_temp), - }, ) + # Ensure deadband between target temperatures. + initial_low_temp = target_low_temp if (target_high_temp - target_low_temp) < min_temp_range: target_low_temp = target_high_temp - min_temp_range @@ -986,14 +1016,25 @@ async def async_service_temperature_set( if target_low_temp < min_temp: raise ServiceValidationError( translation_domain=DOMAIN, - translation_key="range_to_small", + translation_key="outside_tolerance", translation_placeholders={ - "min_temp": str(target_low_temp), - "max_temp": str(target_high_temp), + "min_temp": str(min_temp), + "target_low": str(target_low_temp), "range": str(min_temp_range), }, ) + _LOGGER.debug( + "Moved low temperature from %d %s to %d %s due to range %d and high temperature is %d %s", + initial_low_temp, + temp_unit, + target_low_temp, + temp_unit, + min_temp_range, + target_high_temp, + temp_unit, + ) + kwargs[ATTR_TARGET_TEMP_HIGH] = target_high_temp kwargs[ATTR_TARGET_TEMP_LOW] = target_low_temp diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 1af21815b9f35d..7f790dac25a91a 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -269,6 +269,12 @@ }, "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 low temperature can not be higher than target high temperature." + }, + "outside_tolerance": { + "message": "Ensuring range of {range} made target low temperature {target_low} move out of the allowed minimum temperature {min_temp}." } } } diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index cae196c14b66c9..910fc7e4ef5d50 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1292,7 +1292,7 @@ async def async_setup_entry_climate_platform( async def test_temperature_range_deadband( - hass: HomeAssistant, config_flow_fixture: None + hass: HomeAssistant, config_flow_fixture: None, caplog: pytest.LogCaptureFixture ) -> None: """Test temperature range with deadband.""" @@ -1305,6 +1305,7 @@ class MockClimateEntityTargetTemp(MockClimateEntity): | ClimateEntityFeature.SWING_MODE | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) + _attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_min_temperature_range = 3 _attr_target_temperature_high = 16 _attr_target_temperature_low = 11 @@ -1401,6 +1402,11 @@ async def async_setup_entry_climate_platform( assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 20.0 assert state.attributes[ATTR_MIN_TEMP_RANGE] == 3.0 + assert ( + "Moved low temperature from 18 °C to 17 °C due to range 3 and high temperature is 20 °C" + in caplog.text + ) + # Raises as not allowed for min temp to be higher than high temp with pytest.raises(ServiceValidationError): await hass.services.async_call( @@ -1426,3 +1432,52 @@ async def async_setup_entry_climate_platform( }, blocking=True, ) + + hass.config.units.temperature_unit = UnitOfTemperature.FAHRENHEIT + + # Does not raise as 5 degrees Fahrenheit is within the 3 degrees Celsius range + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test", + ATTR_TARGET_TEMP_HIGH: 60, # 15.5 C + ATTR_TARGET_TEMP_LOW: 55, # 12.7 C + }, + blocking=True, + ) + state = hass.states.get("climate.test") + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 55.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 60.0 + assert state.attributes[ATTR_MIN_TEMP_RANGE] == 5.0 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test", + ATTR_TARGET_TEMP_HIGH: 60, + ATTR_TARGET_TEMP_LOW: 56, + }, + blocking=True, + ) + state = hass.states.get("climate.test") + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 55.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 60.0 + assert state.attributes[ATTR_MIN_TEMP_RANGE] == 5.0 + + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test", + ATTR_TARGET_TEMP_HIGH: 54, + ATTR_TARGET_TEMP_LOW: 50, + }, + blocking=True, + ) + state = hass.states.get("climate.test") + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 55.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 60.0 + assert state.attributes[ATTR_MIN_TEMP_RANGE] == 5.0