mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 15:30:21 +00:00
133 lines
5.0 KiB
Python
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
|