Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Climate set temp action for incorrect feature will raise #126692

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 6 additions & 34 deletions homeassistant/components/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/climate/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
}
60 changes: 27 additions & 33 deletions tests/components/climate/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,40 +290,34 @@ def supported_features(self) -> int:
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(
Expand Down
2 changes: 1 addition & 1 deletion tests/components/deconz/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
52 changes: 24 additions & 28 deletions tests/components/esphome/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
ClimateState,
ClimateSwingMode,
)
import pytest
from syrupy import SnapshotAssertion

from homeassistant.components.climate import (
Expand Down Expand Up @@ -41,6 +42,7 @@
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError


async def test_climate_entity(
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
gjohansson-ST marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down
9 changes: 0 additions & 9 deletions tests/components/fritzbox/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
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,
Expand Down Expand Up @@ -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(
Expand Down
7 changes: 0 additions & 7 deletions tests/components/homematicip_cloud/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 13 additions & 10 deletions tests/components/lcn/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 (
Expand All @@ -25,6 +26,7 @@
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
Expand Down Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion tests/components/maxcube/test_maxcube_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 0 additions & 28 deletions tests/components/shelly/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading