mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 19:06:37 +00:00
[core] Report unknown for switch, climate and water heater with no state (#19458)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
14bd50d469
commit
a1b0e85a9e
@@ -0,0 +1,132 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user