From 3364e945aabbec646e52104403b43b5378e422f9 Mon Sep 17 00:00:00 2001 From: Jacob Southard Date: Wed, 13 Jan 2021 08:21:32 -0600 Subject: [PATCH] Fix HomeKit climate integration for devices with a single set point in Heat_Cool mode. (#45065) * Check supported flags in auto mode, and add tests. * Fix test description. --- .../components/homekit_controller/climate.py | 53 +++++++--- .../homekit_controller/test_climate.py | 100 ++++++++++++++++++ 2 files changed, 139 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 042bc4771c1..cb0feb6ba77 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -346,7 +346,9 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): heat_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}: + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( + SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + ): if temp is None: temp = (cool_temp + heat_temp) / 2 await self.async_put_characteristics( @@ -386,37 +388,54 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def target_temperature(self): """Return the temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) not in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: - return None - return self.service.value(CharacteristicsTypes.TEMPERATURE_TARGET) + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}) or ( + (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) + and not (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features) + ): + return self.service.value(CharacteristicsTypes.TEMPERATURE_TARGET) + return None @property def target_temperature_high(self): """Return the highbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) not in {HVAC_MODE_HEAT_COOL}: - return None - return self.service.value(CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD) + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( + SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + ): + return self.service.value( + CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD + ) + return None @property def target_temperature_low(self): """Return the lowbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) not in {HVAC_MODE_HEAT_COOL}: - return None - return self.service.value(CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD) + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( + SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + ): + return self.service.value( + CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD + ) + return None @property def min_temp(self): """Return the minimum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}: + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( + SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + ): min_temp = self.service[ CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD ].minValue if min_temp is not None: return min_temp - if MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: + elif MODE_HOMEKIT_TO_HASS.get(value) in { + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + }: min_temp = self.service[CharacteristicsTypes.TEMPERATURE_TARGET].minValue if min_temp is not None: return min_temp @@ -426,13 +445,19 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def max_temp(self): """Return the maximum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}: + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( + SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + ): max_temp = self.service[ CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD ].maxValue if max_temp is not None: return max_temp - if MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: + elif MODE_HOMEKIT_TO_HASS.get(value) in { + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + }: max_temp = self.service[CharacteristicsTypes.TEMPERATURE_TARGET].maxValue if max_temp is not None: return max_temp diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py index d3f852d7a49..52671703cca 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -283,6 +283,106 @@ async def test_climate_cannot_set_thermostat_temp_range_in_wrong_mode(hass, utcn assert helper.characteristics[THERMOSTAT_TEMPERATURE_COOLING_THRESHOLD].value == 0 +def create_thermostat_single_set_point_auto(accessory): + """Define thermostat characteristics with a single set point in auto.""" + service = accessory.add_service(ServicesTypes.THERMOSTAT) + + char = service.add_char(CharacteristicsTypes.HEATING_COOLING_TARGET) + char.value = 0 + + char = service.add_char(CharacteristicsTypes.HEATING_COOLING_CURRENT) + char.value = 0 + + char = service.add_char(CharacteristicsTypes.TEMPERATURE_TARGET) + char.minValue = 7 + char.maxValue = 35 + char.value = 0 + + char = service.add_char(CharacteristicsTypes.TEMPERATURE_CURRENT) + char.value = 0 + + char = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET) + char.value = 0 + + char = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) + char.value = 0 + + +async def test_climate_check_min_max_values_per_mode_sspa_device(hass, utcnow): + """Test appropriate min/max values for each mode on sspa devices.""" + helper = await setup_test_component(hass, create_thermostat_single_set_point_auto) + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT}, + blocking=True, + ) + climate_state = await helper.poll_and_get_state() + assert climate_state.attributes["min_temp"] == 7 + assert climate_state.attributes["max_temp"] == 35 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_COOL}, + blocking=True, + ) + climate_state = await helper.poll_and_get_state() + assert climate_state.attributes["min_temp"] == 7 + assert climate_state.attributes["max_temp"] == 35 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT_COOL}, + blocking=True, + ) + climate_state = await helper.poll_and_get_state() + assert climate_state.attributes["min_temp"] == 7 + assert climate_state.attributes["max_temp"] == 35 + + +async def test_climate_set_thermostat_temp_on_sspa_device(hass, utcnow): + """Test setting temperature in different modes on device with single set point in auto.""" + helper = await setup_test_component(hass, create_thermostat_single_set_point_auto) + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT}, + blocking=True, + ) + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {"entity_id": "climate.testdevice", "temperature": 21}, + blocking=True, + ) + assert helper.characteristics[TEMPERATURE_TARGET].value == 21 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {"entity_id": "climate.testdevice", "hvac_mode": HVAC_MODE_HEAT_COOL}, + blocking=True, + ) + assert helper.characteristics[TEMPERATURE_TARGET].value == 21 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.testdevice", + "hvac_mode": HVAC_MODE_HEAT_COOL, + "temperature": 22, + }, + blocking=True, + ) + assert helper.characteristics[TEMPERATURE_TARGET].value == 22 + + async def test_climate_change_thermostat_humidity(hass, utcnow): """Test that we can turn a HomeKit thermostat on and off again.""" helper = await setup_test_component(hass, create_thermostat_service)