Files
esphome/tests/integration/test_entity_missing_state.py
T

133 lines
5.0 KiB
Python

"""Integration test for missing_state on switch, climate and water heater.
These three entity types always sent a concrete state, so a client could not
tell a real OFF from an entity whose value has not been read yet. They now
report missing_state until something publishes, like every other stateful
entity does.
"""
from __future__ import annotations
import asyncio
from aioesphomeapi import (
ClimateInfo,
ClimateMode,
ClimateState,
EntityInfo,
EntityState,
SwitchInfo,
SwitchState,
WaterHeaterInfo,
WaterHeaterMode,
WaterHeaterState,
)
import pytest
from .state_utils import InitialStateHelper, require_entity
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_entity_missing_state(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test that a switch, climate and water heater start out unknown.
This verifies that:
1. They report missing_state=True while nothing has published
2. Publishing clears missing_state and reports the published value
3. A first value that happens to equal the default still publishes
"""
loop = asyncio.get_running_loop()
futures: dict[int, asyncio.Future[EntityState]] = {}
def on_state(state: EntityState) -> None:
"""Resolve the pending future for the entity that changed."""
future = futures.get(state.key)
if future is not None and not future.done():
future.set_result(state)
async with (
run_compiled(yaml_config),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
switch_info = require_entity(entities, "test_switch", SwitchInfo)
climate_info = require_entity(entities, "test_climate", ClimateInfo)
initial_climate_info = require_entity(entities, "initial_climate", ClimateInfo)
water_heater_info = require_entity(
entities, "test_water_heater", WaterHeaterInfo
)
lambda_water_heater_info = require_entity(
entities, "lambda_water_heater", WaterHeaterInfo
)
publish_button = require_entity(
entities, "publish_states", description="Publish States button"
)
stateful: list[EntityInfo] = [
switch_info,
climate_info,
water_heater_info,
lambda_water_heater_info,
]
initial_state_helper = InitialStateHelper(entities)
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
try:
await initial_state_helper.wait_for_initial_states()
except TimeoutError:
pytest.fail("Timeout waiting for initial states")
# Nothing has published yet, so all of them must report unknown
for info in stateful:
state = initial_state_helper.initial_states.get(info.key)
assert state is not None, f"No initial state received for {info.object_id}"
assert state.missing_state is True, (
f"Initial state for {info.object_id} should have "
f"missing_state=True, got {state}"
)
# A configured initial_state must not start out unknown
initial_climate_state = initial_state_helper.initial_states.get(
initial_climate_info.key
)
assert isinstance(initial_climate_state, ClimateState)
assert initial_climate_state.missing_state is False
assert initial_climate_state.mode is ClimateMode.HEAT
# Publishing a state on each one clears missing_state
futures = {info.key: loop.create_future() for info in stateful}
client.button_command(publish_button.key)
try:
await asyncio.wait_for(asyncio.gather(*futures.values()), timeout=5.0)
except TimeoutError:
pytest.fail("Timeout waiting for published states")
switch_state = futures[switch_info.key].result()
assert isinstance(switch_state, SwitchState)
assert switch_state.missing_state is False
assert switch_state.state is True
climate_state = futures[climate_info.key].result()
assert isinstance(climate_state, ClimateState)
assert climate_state.missing_state is False
assert climate_state.mode is ClimateMode.HEAT
water_heater_state = futures[water_heater_info.key].result()
assert isinstance(water_heater_state, WaterHeaterState)
assert water_heater_state.missing_state is False
assert water_heater_state.mode is WaterHeaterMode.ECO
# Its first value is OFF, which is also the default the entity starts on,
# so it only leaves unknown if the first value publishes regardless
lambda_state = futures[lambda_water_heater_info.key].result()
assert isinstance(lambda_state, WaterHeaterState)
assert lambda_state.missing_state is False
assert lambda_state.mode is WaterHeaterMode.OFF