diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index c36a32b0d5b..95c5f87b6c2 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -245,7 +245,7 @@ class Thermostat(HomeAccessory): def _set_chars(self, char_values): _LOGGER.debug("Thermostat _set_chars: %s", char_values) events = [] - params = {} + params = {ATTR_ENTITY_ID: self.entity_id} service = None state = self.hass.states.get(self.entity_id) features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) @@ -285,12 +285,20 @@ class Thermostat(HomeAccessory): target_hc = hc_fallback break - service = SERVICE_SET_HVAC_MODE_THERMOSTAT - hass_value = self.hc_homekit_to_hass[target_hc] - params = {ATTR_HVAC_MODE: hass_value} + params[ATTR_HVAC_MODE] = self.hc_homekit_to_hass[target_hc] events.append( f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}" ) + # Many integrations do not actually implement `hvac_mode` for the + # `SERVICE_SET_TEMPERATURE_THERMOSTAT` service so we made a call to + # `SERVICE_SET_HVAC_MODE_THERMOSTAT` before calling `SERVICE_SET_TEMPERATURE_THERMOSTAT` + # to ensure the device is in the right mode before setting the temp. + self.async_call_service( + DOMAIN_CLIMATE, + SERVICE_SET_HVAC_MODE_THERMOSTAT, + params.copy(), + ", ".join(events), + ) if CHAR_TARGET_TEMPERATURE in char_values: hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE] @@ -357,7 +365,6 @@ class Thermostat(HomeAccessory): ) if service: - params[ATTR_ENTITY_ID] = self.entity_id self.async_call_service( DOMAIN_CLIMATE, service, diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index d9c2a6bf0ed..e73465b0ab0 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -560,6 +560,119 @@ async def test_thermostat_auto(hass, hk_driver, events): ) +async def test_thermostat_mode_and_temp_change(hass, hk_driver, events): + """Test if accessory where the mode and temp change in the same call.""" + 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: [ + 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() + acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run() + 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 + + hass.states.async_set( + entity_id, + HVAC_MODE_COOL, + { + ATTR_TARGET_TEMP_HIGH: 23.0, + ATTR_TARGET_TEMP_LOW: 19.0, + ATTR_CURRENT_TEMPERATURE: 21.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() + assert acc.char_heating_thresh_temp.value == 19.0 + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_current_heat_cool.value == HC_HEAT_COOL_COOL + assert acc.char_target_heat_cool.value == HC_HEAT_COOL_COOL + assert acc.char_current_temp.value == 21.0 + assert acc.char_display_units.value == 0 + + # Set from HomeKit + call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature") + call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode") + + char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID] + char_cooling_thresh_temp_iid = acc.char_cooling_thresh_temp.to_HAP()[HAP_REPR_IID] + char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_heating_thresh_temp_iid, + HAP_REPR_VALUE: 20.0, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_cooling_thresh_temp_iid, + HAP_REPR_VALUE: 25.0, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_target_heat_cool_iid, + HAP_REPR_VALUE: HC_HEAT_COOL_AUTO, + }, + ] + }, + "mock_addr", + ) + + await hass.async_block_till_done() + assert call_set_hvac_mode[0] + assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT_COOL + assert call_set_temperature[0] + assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id + assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 20.0 + assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 25.0 + assert acc.char_heating_thresh_temp.value == 20.0 + assert acc.char_cooling_thresh_temp.value == 25.0 + assert len(events) == 2 + assert events[-2].data[ATTR_VALUE] == "TargetHeatingCoolingState to 3" + assert ( + events[-1].data[ATTR_VALUE] + == "TargetHeatingCoolingState to 3, CoolingThresholdTemperature to 25.0°C, HeatingThresholdTemperature to 20.0°C" + ) + + async def test_thermostat_humidity(hass, hk_driver, events): """Test if accessory and HA are updated accordingly with humidity.""" entity_id = "climate.test"