Compare commits

...

14 Commits

Author SHA1 Message Date
Ludovic BOUÉ
72ababf5c2 Modifies the async_set_temperature method to execute logic synchronously in the event loop, to avoid thread safety violations with legacy synchronous implementations. 2025-11-22 21:04:02 +00:00
Ludovic BOUÉ
e100d8c53e Improve target temperature management by executing the adjustment method in an executor to avoid blocking the event loop. 2025-11-22 20:40:10 +00:00
Ludovic BOUÉ
a678bed3b2 Execute the temperature definition directly in the event loop to ensure thread safety. 2025-11-22 18:18:50 +00:00
Ludovic BOUÉ
ff8a4463ab Add unit_of_measurement for temperature fields 2025-11-22 18:50:15 +01:00
Ludovic BOUÉ
4d1aa15ddf Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-22 18:48:59 +01:00
Ludovic BOUÉ
d91e7f060c Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-22 18:48:37 +01:00
Ludovic BOUÉ
20c99ecf04 Added support for the target temperature range in the temperature control service 2025-11-22 17:03:36 +00:00
Ludovic BOUÉ
6a711ee423 Reorder the name and description fields 2025-11-22 16:57:48 +00:00
Ludovic BOUÉ
fc9e131297 Fix JSON formatting in strings.json 2025-11-22 17:43:32 +01:00
Ludovic BOUÉ
abafd20a3a Merge branch 'dev' into gj-20250705-02 2025-11-22 17:41:15 +01:00
G Johansson
d5c244395c Fix reproduce state 2025-08-03 10:31:23 +00:00
G Johansson
eeaa7dfb4d Add to demo 2025-08-03 10:31:23 +00:00
G Johansson
6c64615e0f Add tests 2025-08-03 10:31:23 +00:00
G Johansson
7379de033f Add target temp range to water_heater 2025-08-03 10:31:23 +00:00
8 changed files with 394 additions and 30 deletions

View File

@@ -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."""

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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