From e01ceb1a57575308ea33f51f433db8fb0b042fd8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 02:09:33 -0500 Subject: [PATCH] Fix handling homekit thermostat states (#34905) --- .../components/homekit/type_thermostats.py | 146 +++++++---- .../homekit/test_type_thermostats.py | 231 +++++++++++++++++- 2 files changed, 319 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 0a488917381..0da92ef3dba 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -117,12 +117,15 @@ class Thermostat(HomeAccessory): """Initialize a Thermostat accessory object.""" super().__init__(*args, category=CATEGORY_THERMOSTAT) self._unit = self.hass.config.units.temperature_unit + self._state_updates = 0 + self.hc_homekit_to_hass = None + self.hc_hass_to_homekit = None min_temp, max_temp = self.get_temperature_range() # Homekit only supports 10-38, overwriting - # the max to appears to work, but less than 10 causes + # the max to appears to work, but less than 0 causes # a crash on the home app - hc_min_temp = max(min_temp, HC_MIN_TEMP) + hc_min_temp = max(min_temp, 0) hc_max_temp = max_temp min_humidity = self.hass.states.get(self.entity_id).attributes.get( @@ -149,48 +152,17 @@ class Thermostat(HomeAccessory): CHAR_CURRENT_HEATING_COOLING, value=0 ) - # Target mode characteristics - hc_modes = state.attributes.get(ATTR_HVAC_MODES) - if hc_modes is None: - _LOGGER.error( - "%s: HVAC modes not yet available. Please disable auto start for homekit.", - self.entity_id, - ) - hc_modes = ( - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - ) - - # Determine available modes for this entity, - # Prefer HEAT_COOL over AUTO and COOL over FAN_ONLY, DRY - # - # HEAT_COOL is preferred over auto because HomeKit Accessory Protocol describes - # heating or cooling comes on to maintain a target temp which is closest to - # the Home Assistant spec - # - # HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range - self.hc_homekit_to_hass = { - c: s - for s, c in HC_HASS_TO_HOMEKIT.items() - if ( - s in hc_modes - and not ( - (s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes) - or ( - s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY) - and HVAC_MODE_COOL in hc_modes - ) - ) - ) - } - hc_valid_values = {k: v for v, k in self.hc_homekit_to_hass.items()} - + self._configure_hvac_modes(state) + # Must set the value first as setting + # valid_values happens before setting + # the value and if 0 is not a valid + # value this will throw self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, valid_values=hc_valid_values, + CHAR_TARGET_HEATING_COOLING, value=list(self.hc_homekit_to_hass)[0] + ) + self.char_target_heat_cool.override_properties( + valid_values=self.hc_hass_to_homekit ) - # Current and target temperature characteristics self.char_current_temp = serv_thermostat.configure_char( @@ -249,7 +221,7 @@ class Thermostat(HomeAccessory): CHAR_CURRENT_HUMIDITY, value=50 ) - self.update_state(state) + self._update_state(state) serv_thermostat.setter_callback = self._set_chars @@ -356,6 +328,46 @@ class Thermostat(HomeAccessory): if CHAR_TARGET_HUMIDITY in char_values: self.set_target_humidity(char_values[CHAR_TARGET_HUMIDITY]) + def _configure_hvac_modes(self, state): + """Configure target mode characteristics.""" + hc_modes = state.attributes.get(ATTR_HVAC_MODES) + if not hc_modes: + # This cannot be none OR an empty list + _LOGGER.error( + "%s: HVAC modes not yet available. Please disable auto start for homekit.", + self.entity_id, + ) + hc_modes = ( + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + ) + + # Determine available modes for this entity, + # Prefer HEAT_COOL over AUTO and COOL over FAN_ONLY, DRY + # + # HEAT_COOL is preferred over auto because HomeKit Accessory Protocol describes + # heating or cooling comes on to maintain a target temp which is closest to + # the Home Assistant spec + # + # HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range + self.hc_homekit_to_hass = { + c: s + for s, c in HC_HASS_TO_HOMEKIT.items() + if ( + s in hc_modes + and not ( + (s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes) + or ( + s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY) + and HVAC_MODE_COOL in hc_modes + ) + ) + ) + } + self.hc_hass_to_homekit = {k: v for v, k in self.hc_homekit_to_hass.items()} + def get_temperature_range(self): """Return min and max temperature range.""" max_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MAX_TEMP) @@ -382,14 +394,46 @@ class Thermostat(HomeAccessory): def update_state(self, new_state): """Update thermostat state after state changed.""" + if self._state_updates < 3: + # When we get the first state updates + # we recheck valid hvac modes as the entity + # may not have been fully setup when we saw it the + # first time + original_hc_hass_to_homekit = self.hc_hass_to_homekit + self._configure_hvac_modes(new_state) + if self.hc_hass_to_homekit != original_hc_hass_to_homekit: + if self.char_target_heat_cool.value not in self.hc_homekit_to_hass: + # We must make sure the char value is + # in the new valid values before + # setting the new valid values or + # changing them with throw + self.char_target_heat_cool.set_value( + list(self.hc_homekit_to_hass)[0], should_notify=False + ) + self.char_target_heat_cool.override_properties( + valid_values=self.hc_hass_to_homekit + ) + self._state_updates += 1 + + self._update_state(new_state) + + def _update_state(self, new_state): + """Update state without rechecking the device features.""" features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) # Update target operation mode FIRST hvac_mode = new_state.state if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT: homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode] - if self.char_target_heat_cool.value != homekit_hvac_mode: - self.char_target_heat_cool.set_value(homekit_hvac_mode) + if homekit_hvac_mode in self.hc_homekit_to_hass: + if self.char_target_heat_cool.value != homekit_hvac_mode: + self.char_target_heat_cool.set_value(homekit_hvac_mode) + else: + _LOGGER.error( + "Cannot map hvac target mode: %s to homekit as only %s modes are supported", + hvac_mode, + self.hc_homekit_to_hass, + ) # Set current operation mode for supported thermostats hvac_action = new_state.attributes.get(ATTR_HVAC_ACTION) @@ -444,13 +488,13 @@ class Thermostat(HomeAccessory): # even if the device does not support it hc_hvac_mode = self.char_target_heat_cool.value if hc_hvac_mode == HC_HEAT_COOL_HEAT: - target_temp = self._temperature_to_homekit( - new_state.attributes.get(ATTR_TARGET_TEMP_LOW) - ) + temp_low = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) + if isinstance(temp_low, (int, float)): + target_temp = self._temperature_to_homekit(temp_low) elif hc_hvac_mode == HC_HEAT_COOL_COOL: - target_temp = self._temperature_to_homekit( - new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) - ) + temp_high = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) + if isinstance(temp_high, (int, float)): + target_temp = self._temperature_to_homekit(temp_high) if target_temp and self.char_target_temp.value != target_temp: self.char_target_temp.set_value(target_temp) diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 8ee533521e8..82abed32c0e 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -43,7 +43,6 @@ from homeassistant.components.homekit.const import ( PROP_MIN_STEP, PROP_MIN_VALUE, ) -from homeassistant.components.homekit.type_thermostats import HC_MIN_TEMP from homeassistant.components.water_heater import DOMAIN as DOMAIN_WATER_HEATER from homeassistant.const import ( ATTR_ENTITY_ID, @@ -116,7 +115,7 @@ async def test_thermostat(hass, hk_driver, cls, events): assert acc.char_current_humidity is None assert acc.char_target_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_target_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_target_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_target_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -126,6 +125,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.2, ATTR_CURRENT_TEMPERATURE: 17.8, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -142,6 +149,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 23.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -158,6 +173,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 20.0, ATTR_CURRENT_TEMPERATURE: 25.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -202,6 +225,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -218,6 +249,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 25.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -234,6 +273,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 22.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -250,6 +297,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 22.0, ATTR_HVAC_ACTION: CURRENT_HVAC_FAN, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -369,7 +424,15 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): HVAC_MODE_OFF, { ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -383,10 +446,10 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): assert acc.char_heating_thresh_temp.value == 19.0 assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -397,6 +460,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 20.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -415,6 +486,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 24.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -433,6 +512,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 21.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1094,10 +1181,10 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e assert acc.char_heating_thresh_temp.value == 19.0 assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -1109,6 +1196,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1128,6 +1223,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 24.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1147,6 +1250,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 21.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1400,3 +1511,109 @@ async def test_water_heater_restore(hass, hk_driver, cls, events): "Heat", "Off", } + + +async def test_thermostat_with_no_modes_when_we_first_see(hass, hk_driver, cls, events): + """Test if a thermostat that is not ready when we first see it.""" + entity_id = "climate.test" + + # support_auto = True + hass.states.async_set( + entity_id, + HVAC_MODE_OFF, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [], + }, + ) + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + + assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + + assert acc.char_target_heat_cool.value == 0 + + hass.states.async_set( + entity_id, + HVAC_MODE_HEAT_COOL, + { + ATTR_TARGET_TEMP_HIGH: 22.0, + ATTR_TARGET_TEMP_LOW: 20.0, + ATTR_CURRENT_TEMPERATURE: 18.0, + ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODE_AUTO], + }, + ) + await hass.async_block_till_done() + assert acc.char_heating_thresh_temp.value == 20.0 + assert acc.char_cooling_thresh_temp.value == 22.0 + assert acc.char_current_heat_cool.value == 1 + assert acc.char_target_heat_cool.value == 3 + assert acc.char_current_temp.value == 18.0 + assert acc.char_display_units.value == 0 + + +async def test_thermostat_with_no_off_after_recheck(hass, hk_driver, cls, events): + """Test if a thermostat that is not ready when we first see it that actually does not have off.""" + entity_id = "climate.test" + + # support_auto = True + hass.states.async_set( + entity_id, + HVAC_MODE_COOL, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [], + }, + ) + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + + assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + + assert acc.char_target_heat_cool.value == 2 + + hass.states.async_set( + entity_id, + HVAC_MODE_HEAT_COOL, + { + ATTR_TARGET_TEMP_HIGH: 22.0, + ATTR_TARGET_TEMP_LOW: 20.0, + ATTR_CURRENT_TEMPERATURE: 18.0, + ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO], + }, + ) + await hass.async_block_till_done() + assert acc.char_heating_thresh_temp.value == 20.0 + assert acc.char_cooling_thresh_temp.value == 22.0 + assert acc.char_current_heat_cool.value == 1 + assert acc.char_target_heat_cool.value == 3 + assert acc.char_current_temp.value == 18.0 + assert acc.char_display_units.value == 0