mirror of
https://github.com/home-assistant/core.git
synced 2025-12-04 06:58:33 +00:00
Compare commits
14 Commits
knx-data-s
...
gj-2025070
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72ababf5c2 | ||
|
|
e100d8c53e | ||
|
|
a678bed3b2 | ||
|
|
ff8a4463ab | ||
|
|
4d1aa15ddf | ||
|
|
d91e7f060c | ||
|
|
20c99ecf04 | ||
|
|
6a711ee423 | ||
|
|
fc9e131297 | ||
|
|
abafd20a3a | ||
|
|
d5c244395c | ||
|
|
eeaa7dfb4d | ||
|
|
6c64615e0f | ||
|
|
7379de033f |
@@ -30,11 +30,27 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
DemoWaterHeater(
|
||||
"Demo Water Heater", 119, UnitOfTemperature.FAHRENHEIT, False, "eco", 1
|
||||
"Demo Water Heater",
|
||||
119,
|
||||
None,
|
||||
UnitOfTemperature.FAHRENHEIT,
|
||||
False,
|
||||
"eco",
|
||||
1,
|
||||
),
|
||||
DemoWaterHeater(
|
||||
"Demo Water Heater Celsius",
|
||||
45,
|
||||
None,
|
||||
UnitOfTemperature.CELSIUS,
|
||||
True,
|
||||
"eco",
|
||||
1,
|
||||
),
|
||||
DemoWaterHeater(
|
||||
"Demo Water Heater Range",
|
||||
None,
|
||||
(45, 60),
|
||||
UnitOfTemperature.CELSIUS,
|
||||
True,
|
||||
"eco",
|
||||
@@ -53,7 +69,8 @@ class DemoWaterHeater(WaterHeaterEntity):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
target_temperature: int,
|
||||
target_temperature: int | None,
|
||||
target_temperature_range: tuple[int, int] | None,
|
||||
unit_of_measurement: str,
|
||||
away: bool,
|
||||
current_operation: str,
|
||||
@@ -63,11 +80,21 @@ class DemoWaterHeater(WaterHeaterEntity):
|
||||
self._attr_name = name
|
||||
if target_temperature is not None:
|
||||
self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
if target_temperature_range is not None:
|
||||
self._attr_supported_features |= (
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
)
|
||||
if away is not None:
|
||||
self._attr_supported_features |= WaterHeaterEntityFeature.AWAY_MODE
|
||||
if current_operation is not None:
|
||||
self._attr_supported_features |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||
self._attr_target_temperature = target_temperature
|
||||
self._attr_target_temperature_low = (
|
||||
target_temperature_range[0] if target_temperature_range else None
|
||||
)
|
||||
self._attr_target_temperature_high = (
|
||||
target_temperature_range[1] if target_temperature_range else None
|
||||
)
|
||||
self._attr_temperature_unit = unit_of_measurement
|
||||
self._attr_is_away_mode_on = away
|
||||
self._attr_current_operation = current_operation
|
||||
@@ -85,7 +112,6 @@ class DemoWaterHeater(WaterHeaterEntity):
|
||||
def set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperatures."""
|
||||
self._attr_target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set new operation mode."""
|
||||
|
||||
@@ -68,6 +68,7 @@ class WaterHeaterEntityFeature(IntFlag):
|
||||
OPERATION_MODE = 2
|
||||
AWAY_MODE = 4
|
||||
ON_OFF = 8
|
||||
TARGET_TEMPERATURE_RANGE = 16
|
||||
|
||||
|
||||
ATTR_MAX_TEMP = "max_temp"
|
||||
@@ -80,17 +81,26 @@ ATTR_TARGET_TEMP_LOW = "target_temp_low"
|
||||
ATTR_TARGET_TEMP_STEP = "target_temp_step"
|
||||
ATTR_CURRENT_TEMPERATURE = "current_temperature"
|
||||
|
||||
CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE]
|
||||
CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SET_AWAY_MODE_SCHEMA: VolDictType = {
|
||||
vol.Required(ATTR_AWAY_MODE): cv.boolean,
|
||||
}
|
||||
SET_TEMPERATURE_SCHEMA: VolDictType = {
|
||||
vol.Required(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float),
|
||||
vol.Optional(ATTR_OPERATION_MODE): cv.string,
|
||||
}
|
||||
SET_TEMPERATURE_SCHEMA = vol.All(
|
||||
cv.has_at_least_one_key(
|
||||
ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW
|
||||
),
|
||||
cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float),
|
||||
vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float),
|
||||
vol.Optional(ATTR_OPERATION_MODE): cv.string,
|
||||
}
|
||||
),
|
||||
)
|
||||
SET_OPERATION_MODE_SCHEMA: VolDictType = {
|
||||
vol.Required(ATTR_OPERATION_MODE): cv.string,
|
||||
}
|
||||
@@ -121,7 +131,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SET_TEMPERATURE_SCHEMA,
|
||||
async_service_temperature_set,
|
||||
[WaterHeaterEntityFeature.TARGET_TEMPERATURE],
|
||||
[
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE,
|
||||
WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||
],
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
@@ -322,9 +335,18 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
await self.hass.async_add_executor_job(
|
||||
ft.partial(self.set_temperature, **kwargs)
|
||||
)
|
||||
# NOTE: Executed synchronously in the event loop so that legacy sync
|
||||
# implementations (including tests) which incorrectly call
|
||||
# async_write_ha_state from set_temperature do not trigger thread-safety
|
||||
# violations. This is a transitional behavior.
|
||||
# TODO(2026.1): Enforce async implementations or restore executor usage.
|
||||
# Integrations performing blocking I/O (network / disk) MUST override
|
||||
# this method with a true async version or wrap their blocking calls in
|
||||
# hass.async_add_executor_job to avoid blocking the event loop.
|
||||
self.set_temperature(**kwargs)
|
||||
# If the sync implementation did not schedule a state update itself,
|
||||
# schedule one now.
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the water heater on."""
|
||||
@@ -426,13 +448,34 @@ async def async_service_away_mode(
|
||||
|
||||
|
||||
async def async_service_temperature_set(
|
||||
entity: WaterHeaterEntity, service: ServiceCall
|
||||
entity: WaterHeaterEntity, service_call: ServiceCall
|
||||
) -> None:
|
||||
"""Handle set temperature service."""
|
||||
hass = entity.hass
|
||||
kwargs = {}
|
||||
if (
|
||||
ATTR_TEMPERATURE in service_call.data
|
||||
and not entity.supported_features & WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_target_temperature_entity_feature",
|
||||
)
|
||||
if (
|
||||
(
|
||||
ATTR_TARGET_TEMP_LOW in service_call.data
|
||||
or ATTR_TARGET_TEMP_HIGH in service_call.data
|
||||
)
|
||||
and not entity.supported_features
|
||||
& WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_target_temperature_range_entity_feature",
|
||||
)
|
||||
|
||||
for value, temp in service.data.items():
|
||||
hass = entity.hass
|
||||
kwargs: dict[str, Any] = {}
|
||||
|
||||
for value, temp in service_call.data.items():
|
||||
if value in CONVERTIBLE_ATTRIBUTE:
|
||||
kwargs[value] = TemperatureConverter.convert(
|
||||
temp, hass.config.units.temperature_unit, entity.temperature_unit
|
||||
|
||||
@@ -20,6 +20,8 @@ from homeassistant.core import Context, HomeAssistant, State
|
||||
from . import (
|
||||
ATTR_AWAY_MODE,
|
||||
ATTR_OPERATION_MODE,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
DOMAIN,
|
||||
SERVICE_SET_AWAY_MODE,
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
@@ -69,6 +71,10 @@ async def _async_reproduce_state(
|
||||
cur_state.state == state.state
|
||||
and cur_state.attributes.get(ATTR_TEMPERATURE)
|
||||
== state.attributes.get(ATTR_TEMPERATURE)
|
||||
and cur_state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
== state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
and cur_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
== state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
and cur_state.attributes.get(ATTR_AWAY_MODE)
|
||||
== state.attributes.get(ATTR_AWAY_MODE)
|
||||
):
|
||||
@@ -89,6 +95,25 @@ async def _async_reproduce_state(
|
||||
DOMAIN, service, service_data, context=context, blocking=True
|
||||
)
|
||||
|
||||
if (
|
||||
(temp_low := state.attributes.get(ATTR_TARGET_TEMP_LOW)) is not None
|
||||
and temp_low != cur_state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
) or (
|
||||
(temp_high := state.attributes.get(ATTR_TARGET_TEMP_HIGH)) is not None
|
||||
and temp_high != cur_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: state.entity_id,
|
||||
ATTR_TARGET_TEMP_HIGH: state.attributes.get(ATTR_TARGET_TEMP_HIGH),
|
||||
ATTR_TARGET_TEMP_LOW: state.attributes.get(ATTR_TARGET_TEMP_LOW),
|
||||
},
|
||||
context=context,
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
if (
|
||||
state.attributes.get(ATTR_TEMPERATURE)
|
||||
!= cur_state.attributes.get(ATTR_TEMPERATURE)
|
||||
|
||||
@@ -14,8 +14,14 @@ set_temperature:
|
||||
target:
|
||||
entity:
|
||||
domain: water_heater
|
||||
supported_features:
|
||||
- water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
- water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
fields:
|
||||
temperature:
|
||||
filter:
|
||||
supported_features:
|
||||
- water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
@@ -24,6 +30,30 @@ set_temperature:
|
||||
step: 0.5
|
||||
mode: box
|
||||
unit_of_measurement: "°"
|
||||
target_temp_high:
|
||||
filter:
|
||||
supported_features:
|
||||
- water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
advanced: true
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 250
|
||||
step: 0.1
|
||||
mode: box
|
||||
unit_of_measurement: "°"
|
||||
target_temp_low:
|
||||
filter:
|
||||
supported_features:
|
||||
- water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
advanced: true
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 250
|
||||
step: 0.1
|
||||
mode: box
|
||||
unit_of_measurement: "°"
|
||||
operation_mode:
|
||||
example: eco
|
||||
selector:
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"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."
|
||||
},
|
||||
"not_valid_operation_mode": {
|
||||
"message": "Operation mode {operation_mode} is not valid for {entity_id}. Valid operation modes are: {operation_list}."
|
||||
},
|
||||
@@ -82,6 +88,14 @@
|
||||
"description": "New value of the operation mode. For a list of possible modes, refer to the integration documentation.",
|
||||
"name": "Operation mode"
|
||||
},
|
||||
"target_temp_high": {
|
||||
"description": "The max temperature setpoint.",
|
||||
"name": "Upper target temperature"
|
||||
},
|
||||
"target_temp_low": {
|
||||
"description": "The min temperature setpoint.",
|
||||
"name": "Lower target temperature"
|
||||
},
|
||||
"temperature": {
|
||||
"description": "New target temperature for the water heater.",
|
||||
"name": "Temperature"
|
||||
|
||||
@@ -4,10 +4,17 @@ from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import mock_config_flow, mock_platform
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockModule,
|
||||
mock_config_flow,
|
||||
mock_integration,
|
||||
mock_platform,
|
||||
)
|
||||
|
||||
|
||||
class MockFlow(ConfigFlow):
|
||||
@@ -21,3 +28,41 @@ def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
|
||||
|
||||
with mock_config_flow("test", MockFlow):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def register_test_integration(
|
||||
hass: HomeAssistant, config_flow_fixture: None
|
||||
) -> MockConfigEntry:
|
||||
"""Provide a mocked integration for tests."""
|
||||
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
async def help_async_setup_entry_init(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Set up test config entry."""
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry, [Platform.WATER_HEATER]
|
||||
)
|
||||
return True
|
||||
|
||||
async def help_async_unload_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Unload test config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(
|
||||
config_entry, [Platform.WATER_HEATER]
|
||||
)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
)
|
||||
|
||||
return config_entry
|
||||
|
||||
@@ -11,8 +11,13 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components import water_heater
|
||||
from homeassistant.components.water_heater import (
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TEMPERATURE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SET_TEMPERATURE_SCHEMA,
|
||||
WaterHeaterEntity,
|
||||
WaterHeaterEntityDescription,
|
||||
@@ -22,17 +27,18 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockEntity,
|
||||
MockModule,
|
||||
MockPlatform,
|
||||
async_mock_service,
|
||||
import_and_test_deprecated_constant,
|
||||
mock_integration,
|
||||
mock_platform,
|
||||
setup_test_component_platform,
|
||||
)
|
||||
|
||||
|
||||
@@ -40,14 +46,13 @@ async def test_set_temp_schema_no_req(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test the set temperature schema with missing required data."""
|
||||
domain = "climate"
|
||||
service = "test_set_temperature"
|
||||
schema = cv.make_entity_service_schema(SET_TEMPERATURE_SCHEMA)
|
||||
calls = async_mock_service(hass, domain, service, schema)
|
||||
schema = SET_TEMPERATURE_SCHEMA
|
||||
calls = async_mock_service(hass, DOMAIN, service, schema)
|
||||
|
||||
data = {"hvac_mode": "off", "entity_id": ["climate.test_id"]}
|
||||
data = {"hvac_mode": "off", "entity_id": ["water_heater.test_id"]}
|
||||
with pytest.raises(vol.Invalid):
|
||||
await hass.services.async_call(domain, service, data)
|
||||
await hass.services.async_call(DOMAIN, service, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
@@ -57,24 +62,23 @@ async def test_set_temp_schema(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test the set temperature schema with ok required data."""
|
||||
domain = "water_heater"
|
||||
service = "test_set_temperature"
|
||||
schema = cv.make_entity_service_schema(SET_TEMPERATURE_SCHEMA)
|
||||
calls = async_mock_service(hass, domain, service, schema)
|
||||
schema = SET_TEMPERATURE_SCHEMA
|
||||
calls = async_mock_service(hass, DOMAIN, service, schema)
|
||||
|
||||
data = {
|
||||
"temperature": 20.0,
|
||||
"operation_mode": "gas",
|
||||
"entity_id": ["water_heater.test_id"],
|
||||
}
|
||||
await hass.services.async_call(domain, service, data)
|
||||
await hass.services.async_call(DOMAIN, service, data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[-1].data == data
|
||||
|
||||
|
||||
class MockWaterHeaterEntity(WaterHeaterEntity):
|
||||
class MockWaterHeaterEntity(MockEntity, WaterHeaterEntity):
|
||||
"""Mock water heater device to use in tests."""
|
||||
|
||||
_attr_operation_list: list[str] | None = ["off", "heat_pump", "gas"]
|
||||
@@ -237,3 +241,158 @@ def test_deprecated_constants(
|
||||
replacement,
|
||||
"2026.1",
|
||||
)
|
||||
|
||||
|
||||
async def test_target_temp(
|
||||
hass: HomeAssistant, register_test_integration: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test set temp service with target temperature."""
|
||||
|
||||
class MockWaterHeaterEntityTemp(MockWaterHeaterEntity):
|
||||
"""Mock Water heater class."""
|
||||
|
||||
_attr_target_temperature = 15
|
||||
_attr_current_temperature = 15
|
||||
_attr_supported_features = (
|
||||
WaterHeaterEntityFeature.ON_OFF
|
||||
| WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
|
||||
def set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
self._attr_target_temperature = kwargs[ATTR_TEMPERATURE]
|
||||
if ATTR_TARGET_TEMP_HIGH in kwargs:
|
||||
self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||
self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW]
|
||||
self.async_write_ha_state()
|
||||
|
||||
test_heater = MockWaterHeaterEntityTemp(
|
||||
name="Test",
|
||||
unique_id="unique_heater_test",
|
||||
)
|
||||
|
||||
setup_test_component_platform(
|
||||
hass, DOMAIN, entities=[test_heater], from_config_entry=True
|
||||
)
|
||||
await hass.config_entries.async_setup(register_test_integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("water_heater.test")
|
||||
assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 15
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 15
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
"entity_id": "water_heater.test",
|
||||
ATTR_TEMPERATURE: "20",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("water_heater.test")
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 20
|
||||
|
||||
with pytest.raises(
|
||||
ServiceValidationError,
|
||||
match="Set temperature action was used with the target temperature low/high parameter but the entity does not support it",
|
||||
) as exc:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
"entity_id": "water_heater.test",
|
||||
ATTR_TARGET_TEMP_HIGH: "20",
|
||||
ATTR_TARGET_TEMP_LOW: "15",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Set temperature action was used with the target temperature low/high parameter but the entity does not support it"
|
||||
)
|
||||
assert (
|
||||
exc.value.translation_key == "missing_target_temperature_range_entity_feature"
|
||||
)
|
||||
|
||||
|
||||
async def test_target_temp_range(
|
||||
hass: HomeAssistant, register_test_integration: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test set temp service with target temperature range."""
|
||||
|
||||
class MockWaterHeaterEntityTemp(MockWaterHeaterEntity):
|
||||
"""Mock Water heater class."""
|
||||
|
||||
_attr_target_temperature = 15
|
||||
_attr_target_temperature_low = 10
|
||||
_attr_target_temperature_high = 20
|
||||
_attr_current_temperature = 15
|
||||
_attr_supported_features = (
|
||||
WaterHeaterEntityFeature.ON_OFF
|
||||
| WaterHeaterEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
)
|
||||
|
||||
def set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
self._attr_target_temperature = kwargs[ATTR_TEMPERATURE]
|
||||
if ATTR_TARGET_TEMP_HIGH in kwargs:
|
||||
self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH]
|
||||
self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW]
|
||||
self.async_write_ha_state()
|
||||
|
||||
test_heater = MockWaterHeaterEntityTemp(
|
||||
name="Test",
|
||||
unique_id="unique_heater_test",
|
||||
)
|
||||
|
||||
setup_test_component_platform(
|
||||
hass, DOMAIN, entities=[test_heater], from_config_entry=True
|
||||
)
|
||||
await hass.config_entries.async_setup(register_test_integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("water_heater.test")
|
||||
assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 15
|
||||
assert state.attributes.get(ATTR_TEMPERATURE) == 15
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 20
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 10
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
"entity_id": "water_heater.test",
|
||||
ATTR_TARGET_TEMP_HIGH: "20",
|
||||
ATTR_TARGET_TEMP_LOW: "15",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("water_heater.test")
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 20
|
||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 15
|
||||
|
||||
with pytest.raises(
|
||||
ServiceValidationError,
|
||||
match="Set temperature action was used with the target temperature parameter but the entity does not support it",
|
||||
) as exc:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
"entity_id": "water_heater.test",
|
||||
ATTR_TEMPERATURE: "15",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Set temperature action was used with the target temperature parameter but the entity does not support it"
|
||||
)
|
||||
assert exc.value.translation_key == "missing_target_temperature_entity_feature"
|
||||
|
||||
@@ -5,6 +5,8 @@ import pytest
|
||||
from homeassistant.components.water_heater import (
|
||||
ATTR_AWAY_MODE,
|
||||
ATTR_OPERATION_MODE,
|
||||
ATTR_TARGET_TEMP_HIGH,
|
||||
ATTR_TARGET_TEMP_LOW,
|
||||
ATTR_TEMPERATURE,
|
||||
SERVICE_SET_AWAY_MODE,
|
||||
SERVICE_SET_OPERATION_MODE,
|
||||
@@ -25,6 +27,11 @@ async def test_reproducing_states(
|
||||
"""Test reproducing Water heater states."""
|
||||
hass.states.async_set("water_heater.entity_off", STATE_OFF, {})
|
||||
hass.states.async_set("water_heater.entity_on", STATE_ON, {ATTR_TEMPERATURE: 45})
|
||||
hass.states.async_set(
|
||||
"water_heater.entity_range",
|
||||
STATE_ON,
|
||||
{ATTR_TARGET_TEMP_HIGH: 45, ATTR_TARGET_TEMP_LOW: 20},
|
||||
)
|
||||
hass.states.async_set("water_heater.entity_away", STATE_ON, {ATTR_AWAY_MODE: True})
|
||||
hass.states.async_set("water_heater.entity_gas", STATE_GAS, {})
|
||||
hass.states.async_set(
|
||||
@@ -45,6 +52,11 @@ async def test_reproducing_states(
|
||||
[
|
||||
State("water_heater.entity_off", STATE_OFF),
|
||||
State("water_heater.entity_on", STATE_ON, {ATTR_TEMPERATURE: 45}),
|
||||
State(
|
||||
"water_heater.entity_range",
|
||||
STATE_ON,
|
||||
{ATTR_TARGET_TEMP_HIGH: 45, ATTR_TARGET_TEMP_LOW: 20},
|
||||
),
|
||||
State("water_heater.entity_away", STATE_ON, {ATTR_AWAY_MODE: True}),
|
||||
State("water_heater.entity_gas", STATE_GAS, {}),
|
||||
State(
|
||||
@@ -79,6 +91,11 @@ async def test_reproducing_states(
|
||||
[
|
||||
State("water_heater.entity_on", STATE_OFF),
|
||||
State("water_heater.entity_off", STATE_ON, {ATTR_TEMPERATURE: 45}),
|
||||
State(
|
||||
"water_heater.entity_range",
|
||||
STATE_ON,
|
||||
{ATTR_TARGET_TEMP_HIGH: 50, ATTR_TARGET_TEMP_LOW: 20},
|
||||
),
|
||||
State("water_heater.entity_all", STATE_ECO, {ATTR_AWAY_MODE: False}),
|
||||
State("water_heater.entity_away", STATE_GAS, {}),
|
||||
State(
|
||||
@@ -112,8 +129,13 @@ async def test_reproducing_states(
|
||||
valid_temp_calls = [
|
||||
{"entity_id": "water_heater.entity_off", ATTR_TEMPERATURE: 45},
|
||||
{"entity_id": "water_heater.entity_gas", ATTR_TEMPERATURE: 45},
|
||||
{
|
||||
"entity_id": "water_heater.entity_range",
|
||||
ATTR_TARGET_TEMP_HIGH: 50,
|
||||
ATTR_TARGET_TEMP_LOW: 20,
|
||||
},
|
||||
]
|
||||
assert len(set_temp_calls) == 2
|
||||
assert len(set_temp_calls) == 3
|
||||
for call in set_temp_calls:
|
||||
assert call.domain == "water_heater"
|
||||
assert call.data in valid_temp_calls
|
||||
|
||||
Reference in New Issue
Block a user