mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 16:00:23 +00:00
Add On/Off and Away mode support to template platform
Implemented support for On/Off and Away modes in the template water heater platform, including optimistic control and lambda-based state reporting. Refactored the base 'WaterHeaterCall' to replace the 'state_' bitmask with 'optional<bool>' for 'on' and 'away' fields. This change was necessary to enable partial (delta) updates. The previous bitmask implementation did not distinguish between a field being "set to false" and "not set at all," causing unintended state resets (e.g., turning the device off when only adjusting temperature).
This commit is contained in:
@@ -11,6 +11,8 @@ water_heater:
|
||||
optimistic: true
|
||||
current_temperature: !lambda "return 45.0f;"
|
||||
target_temperature: !lambda "return 60.0f;"
|
||||
on: !lambda "return true;"
|
||||
away: !lambda "return false;"
|
||||
# Note: No mode lambda - we want optimistic mode changes to stick
|
||||
# A mode lambda would override mode changes in loop()
|
||||
supported_modes:
|
||||
|
||||
@@ -64,6 +64,12 @@ async def test_water_heater_template(
|
||||
f"Expected 4 supported modes, got {len(supported_modes)}: {supported_modes}"
|
||||
)
|
||||
|
||||
# Verify supported features
|
||||
# WATER_HEATER_SUPPORTS_AWAY_MODE (1 << 3) = 8
|
||||
# WATER_HEATER_SUPPORTS_ON_OFF (1 << 4) = 16
|
||||
assert test_water_heater.supported_features & 8
|
||||
assert test_water_heater.supported_features & 16
|
||||
|
||||
# Subscribe with the wrapper that filters initial states
|
||||
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
|
||||
|
||||
@@ -88,6 +94,9 @@ async def test_water_heater_template(
|
||||
assert initial_state.target_temperature == 60.0, (
|
||||
f"Expected target temp 60.0, got {initial_state.target_temperature}"
|
||||
)
|
||||
# Verify On/Away (from lambdas in fixture)
|
||||
assert initial_state.is_on is True
|
||||
assert initial_state.away is False
|
||||
|
||||
# Test changing to GAS mode
|
||||
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS)
|
||||
@@ -100,6 +109,38 @@ async def test_water_heater_template(
|
||||
assert isinstance(gas_state, WaterHeaterState)
|
||||
assert gas_state.mode == WaterHeaterMode.GAS
|
||||
|
||||
# Test changing away mode and power (optimistic)
|
||||
away_future: asyncio.Future[WaterHeaterState] = loop.create_future()
|
||||
off_future: asyncio.Future[WaterHeaterState] = loop.create_future()
|
||||
|
||||
def on_state_update(state: aioesphomeapi.EntityState) -> None:
|
||||
if (
|
||||
isinstance(state, WaterHeaterState)
|
||||
and state.key == test_water_heater.key
|
||||
):
|
||||
if state.away is True and not away_future.done():
|
||||
away_future.set_result(state)
|
||||
if state.is_on is False and not off_future.done():
|
||||
off_future.set_result(state)
|
||||
|
||||
client.subscribe_states(on_state_update)
|
||||
|
||||
# Change away mode
|
||||
client.water_heater_command(test_water_heater.key, away=True)
|
||||
try:
|
||||
away_state = await asyncio.wait_for(away_future, timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("Away mode change not received within 5 seconds")
|
||||
assert away_state.away is True
|
||||
|
||||
# Change power
|
||||
client.water_heater_command(test_water_heater.key, is_on=False)
|
||||
try:
|
||||
off_state = await asyncio.wait_for(off_future, timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("Power off change not received within 5 seconds")
|
||||
assert off_state.is_on is False
|
||||
|
||||
# Test changing to ECO mode (from GAS)
|
||||
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user