diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index cd2ce3b563b..432fbffb843 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -965,46 +965,18 @@ async def async_service_temperature_set( ATTR_TEMPERATURE in service_call.data and not entity.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE ): - # Warning implemented in 2024.10 and will be changed to raising - # a ServiceValidationError in 2025.4 - report_issue = async_suggest_report_issue( - entity.hass, - integration_domain=entity.platform.platform_name, - module=type(entity).__module__, - ) - _LOGGER.warning( - ( - "%s::%s set_temperature action was used with temperature but the entity does not " - "implement the ClimateEntityFeature.TARGET_TEMPERATURE feature. " - "This will stop working in 2025.4 and raise an error instead. " - "Please %s" - ), - entity.platform.platform_name, - entity.__class__.__name__, - report_issue, + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="missing_target_temperature_entity_feature", ) if ( ATTR_TARGET_TEMP_LOW in service_call.data and not entity.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ): - # Warning implemented in 2024.10 and will be changed to raising - # a ServiceValidationError in 2025.4 - report_issue = async_suggest_report_issue( - entity.hass, - integration_domain=entity.platform.platform_name, - module=type(entity).__module__, - ) - _LOGGER.warning( - ( - "%s::%s set_temperature action was used with target_temp_low but the entity does not " - "implement the ClimateEntityFeature.TARGET_TEMPERATURE_RANGE feature. " - "This will stop working in 2025.4 and raise an error instead. " - "Please %s" - ), - entity.platform.platform_name, - entity.__class__.__name__, - report_issue, + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="missing_target_temperature_range_entity_feature", ) hass = entity.hass diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index fc0bdaf0d72..26a06821d84 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -275,6 +275,12 @@ }, "humidity_out_of_range": { "message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}." + }, + "missing_target_temperature_entity_feature": { + "message": "Set temperature action was used with the target temperature parameter but the entity does not support it." + }, + "missing_target_temperature_range_entity_feature": { + "message": "Set temperature action was used with the target temperature low/high parameter but the entity does not support it." } } } diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 2b09c2801df..aa162e0b683 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -290,40 +290,34 @@ async def test_temperature_features_is_valid( await hass.config_entries.async_setup(register_test_integration.entry_id) await hass.async_block_till_done() - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - { - "entity_id": "climate.test_temp", - "temperature": 20, - }, - blocking=True, - ) - assert ( - "MockClimateTempEntity set_temperature action was used " - "with temperature but the entity does not " - "implement the ClimateEntityFeature.TARGET_TEMPERATURE feature. " - "This will stop working in 2025.4 and raise an error instead. " - "Please" - ) in caplog.text + with pytest.raises( + ServiceValidationError, + match="Set temperature action was used with the target temperature parameter but the entity does not support it", + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test_temp", + "temperature": 20, + }, + blocking=True, + ) - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - { - "entity_id": "climate.test_range", - "target_temp_low": 20, - "target_temp_high": 25, - }, - blocking=True, - ) - assert ( - "MockClimateTempRangeEntity set_temperature action was used with " - "target_temp_low but the entity does not " - "implement the ClimateEntityFeature.TARGET_TEMPERATURE_RANGE feature. " - "This will stop working in 2025.4 and raise an error instead. " - "Please" - ) in caplog.text + with pytest.raises( + ServiceValidationError, + match="Set temperature action was used with the target temperature low/high parameter but the entity does not support it", + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test_range", + "target_temp_low": 20, + "target_temp_high": 25, + }, + blocking=True, + ) async def test_mode_validation( diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 7f456e81976..e1000f0b4d6 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -259,7 +259,7 @@ async def test_climate_device_without_cooling_support( # Service set temperature without providing temperature attribute - with pytest.raises(ValueError): + with pytest.raises(ServiceValidationError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/esphome/test_climate.py b/tests/components/esphome/test_climate.py index 4ec7fee6447..189b86fc5fd 100644 --- a/tests/components/esphome/test_climate.py +++ b/tests/components/esphome/test_climate.py @@ -13,6 +13,7 @@ from aioesphomeapi import ( ClimateState, ClimateSwingMode, ) +import pytest from syrupy import SnapshotAssertion from homeassistant.components.climate import ( @@ -41,6 +42,7 @@ from homeassistant.components.climate import ( ) from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError async def test_climate_entity( @@ -54,7 +56,6 @@ async def test_climate_entity( name="my climate", unique_id="my_climate", supports_current_temperature=True, - supports_two_point_target_temperature=True, supports_action=True, visual_min_temperature=10.0, visual_max_temperature=30.0, @@ -134,14 +135,13 @@ async def test_climate_entity_with_step_and_two_point( assert state is not None assert state.state == HVACMode.COOL - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25}, - blocking=True, - ) - mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)]) - mock_client.climate_command.reset_mock() + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25}, + blocking=True, + ) await hass.services.async_call( CLIMATE_DOMAIN, @@ -213,38 +213,34 @@ async def test_climate_entity_with_step_and_target_temp( assert state is not None assert state.state == HVACMode.COOL - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25}, - blocking=True, - ) - mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)]) - mock_client.climate_command.reset_mock() - await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, { ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_HVAC_MODE: HVACMode.AUTO, - ATTR_TARGET_TEMP_LOW: 20, - ATTR_TARGET_TEMP_HIGH: 30, + ATTR_TEMPERATURE: 25, }, blocking=True, ) mock_client.climate_command.assert_has_calls( - [ - call( - key=1, - mode=ClimateMode.AUTO, - target_temperature_low=20.0, - target_temperature_high=30.0, - ) - ] + [call(key=1, mode=ClimateMode.AUTO, target_temperature=25.0)] ) mock_client.climate_command.reset_mock() + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: "climate.test_myclimate", + ATTR_HVAC_MODE: HVACMode.AUTO, + ATTR_TARGET_TEMP_LOW: 20, + ATTR_TARGET_TEMP_HIGH: 30, + }, + blocking=True, + ) + await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index f43e77e9861..61fe6b48a7a 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -15,8 +15,6 @@ from homeassistant.components.climate import ( ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, DOMAIN as CLIMATE_DOMAIN, PRESET_COMFORT, PRESET_ECO, @@ -290,13 +288,6 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: }, [call(23)], ), - ( - { - ATTR_TARGET_TEMP_HIGH: 16, - ATTR_TARGET_TEMP_LOW: 10, - }, - [], - ), ], ) async def test_set_temperature( diff --git a/tests/components/homematicip_cloud/test_climate.py b/tests/components/homematicip_cloud/test_climate.py index c059ed4b744..d4711440288 100644 --- a/tests/components/homematicip_cloud/test_climate.py +++ b/tests/components/homematicip_cloud/test_climate.py @@ -141,13 +141,6 @@ async def test_hmip_heating_group_heat( ha_state = hass.states.get(entity_id) assert ha_state.attributes[ATTR_PRESET_MODE] == "STD" - # Not required for hmip, but a possibility to send no temperature. - await hass.services.async_call( - "climate", - "set_temperature", - {"entity_id": entity_id, "target_temp_low": 10, "target_temp_high": 10}, - blocking=True, - ) # No new service call should be in mock_calls. assert len(hmip_device.mock_calls) == service_call_counter + 12 # Only fire event from last async_manipulate_test_data available. diff --git a/tests/components/lcn/test_climate.py b/tests/components/lcn/test_climate.py index b7fcc2fbe4b..7ba263bd597 100644 --- a/tests/components/lcn/test_climate.py +++ b/tests/components/lcn/test_climate.py @@ -5,6 +5,7 @@ from unittest.mock import patch from pypck.inputs import ModStatusVar, Unknown from pypck.lcn_addr import LcnAddr from pypck.lcn_defs import Var, VarUnit, VarValue +import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.climate import ( @@ -25,6 +26,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import entity_registry as er from .conftest import MockConfigEntry, MockModuleConnection, init_integration @@ -140,16 +142,17 @@ async def test_set_temperature(hass: HomeAssistant, entry: MockConfigEntry) -> N # wrong temperature set via service call with high/low attributes var_abs.return_value = False - await hass.services.async_call( - DOMAIN_CLIMATE, - SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: "climate.climate1", - ATTR_TARGET_TEMP_LOW: 24.5, - ATTR_TARGET_TEMP_HIGH: 25.5, - }, - blocking=True, - ) + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN_CLIMATE, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: "climate.climate1", + ATTR_TARGET_TEMP_LOW: 24.5, + ATTR_TARGET_TEMP_HIGH: 25.5, + }, + blocking=True, + ) var_abs.assert_not_awaited() diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py index 48e616f8fd2..8b56ee6a6de 100644 --- a/tests/components/maxcube/test_maxcube_climate.py +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -216,7 +216,7 @@ async def test_thermostat_set_no_temperature( hass: HomeAssistant, cube: MaxCube, thermostat: MaxThermostat ) -> None: """Set hvac mode to heat.""" - with pytest.raises(ValueError): + with pytest.raises(ServiceValidationError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index 997cf945626..aeeeca30edd 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -13,8 +13,6 @@ from homeassistant.components.climate import ( ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_PRESET_MODE, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, DOMAIN as CLIMATE_DOMAIN, PRESET_NONE, SERVICE_SET_HVAC_MODE, @@ -138,19 +136,6 @@ async def test_climate_set_temperature( assert state.state == HVACMode.OFF assert state.attributes[ATTR_TEMPERATURE] == 4 - # Test set temperature without target temperature - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: ENTITY_ID, - ATTR_TARGET_TEMP_LOW: 20, - ATTR_TARGET_TEMP_HIGH: 30, - }, - blocking=True, - ) - mock_block_device.http_request.assert_not_called() - # Test set temperature await hass.services.async_call( CLIMATE_DOMAIN, @@ -684,19 +669,6 @@ async def test_rpc_climate_set_temperature( state = hass.states.get(entity_id) assert state.attributes[ATTR_TEMPERATURE] == 23 - # test set temperature without target temperature - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: entity_id, - ATTR_TARGET_TEMP_LOW: 20, - ATTR_TARGET_TEMP_HIGH: 30, - }, - blocking=True, - ) - mock_rpc_device.call_rpc.assert_not_called() - monkeypatch.setitem(mock_rpc_device.status["thermostat:0"], "target_C", 28) await hass.services.async_call( CLIMATE_DOMAIN, diff --git a/tests/components/switcher_kis/test_climate.py b/tests/components/switcher_kis/test_climate.py index 5da9684bf2a..c9f7abf34dc 100644 --- a/tests/components/switcher_kis/test_climate.py +++ b/tests/components/switcher_kis/test_climate.py @@ -98,6 +98,10 @@ async def test_climate_temperature( await init_integration(hass) assert mock_bridge + monkeypatch.setattr(DEVICE, "mode", ThermostatMode.HEAT) + mock_bridge.mock_callbacks([DEVICE]) + await hass.async_block_till_done() + # Test initial target temperature state = hass.states.get(ENTITY_ID) assert state.attributes["temperature"] == 23 @@ -126,7 +130,7 @@ async def test_climate_temperature( with patch( "homeassistant.components.switcher_kis.climate.SwitcherType2Api.control_breeze_device", ) as mock_control_device: - with pytest.raises(ValueError): + with pytest.raises(ServiceValidationError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/tesla_fleet/test_climate.py b/tests/components/tesla_fleet/test_climate.py index 75474698d09..b8cb7f1269b 100644 --- a/tests/components/tesla_fleet/test_climate.py +++ b/tests/components/tesla_fleet/test_climate.py @@ -436,7 +436,8 @@ async def test_climate_notemp( await setup_platform(hass, normal_config_entry, [Platform.CLIMATE]) with pytest.raises( - ServiceValidationError, match="Temperature is required for this action" + ServiceValidationError, + match="Set temperature action was used with the target temperature low/high parameter but the entity does not support it", ): await hass.services.async_call( CLIMATE_DOMAIN, diff --git a/tests/components/teslemetry/test_climate.py b/tests/components/teslemetry/test_climate.py index 3cb4b67dc54..800748f4c77 100644 --- a/tests/components/teslemetry/test_climate.py +++ b/tests/components/teslemetry/test_climate.py @@ -10,8 +10,6 @@ from tesla_fleet_api.exceptions import InvalidCommand, VehicleOffline from homeassistant.components.climate import ( ATTR_HVAC_MODE, ATTR_PRESET_MODE, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, ATTR_TEMPERATURE, DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, @@ -175,17 +173,6 @@ async def test_climate( state = hass.states.get(entity_id) assert state.state == HVACMode.COOL - # Set Temp do nothing - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: [entity_id], - ATTR_TARGET_TEMP_HIGH: 30, - ATTR_TARGET_TEMP_LOW: 30, - }, - blocking=True, - ) state = hass.states.get(entity_id) assert state.attributes[ATTR_TEMPERATURE] == 40 assert state.state == HVACMode.COOL