Files
esphome/tests/integration/test_light_restore_state.py
T
2026-09-26 08:47:25 +10:00

117 lines
4.6 KiB
Python

"""Integration test for the light `restore_state:` key.
Tests that:
1. On first boot (nothing saved yet), lights come up at their hardware default (off) --
restore_state overrides never apply before anything is ever saved.
2. After a state is saved and the device restarts, a `restore_state: {}` light comes
back exactly as saved, while a light with explicit overrides applies those overrides
on top of the loaded state regardless of what was saved.
"""
from __future__ import annotations
import asyncio
import socket
from typing import Any
from aioesphomeapi import LightInfo, LightState
import pytest
from .conftest import run_binary_and_wait_for_port, wait_and_connect_api_client
from .host_prefs import clear_host_prefs
from .state_utils import InitialStateHelper, require_entity
from .types import CompileFunction, ConfigWriter
DEVICE_NAME = "host-light-restore-state-test"
@pytest.mark.asyncio
async def test_light_restore_state(
yaml_config: str,
write_yaml_config: ConfigWriter,
compile_esphome: CompileFunction,
reserved_tcp_port: tuple[int, socket.socket],
) -> None:
"""Test restore_state: {} vs restore_state: with overrides, across a restart."""
port, port_socket = reserved_tcp_port
clear_host_prefs(DEVICE_NAME)
config_path = await write_yaml_config(yaml_config)
binary_path = await compile_esphome(config_path)
port_socket.close()
# --- First run: nothing saved yet, both lights must come up off ---
async with (
run_binary_and_wait_for_port(binary_path, "127.0.0.1", port),
wait_and_connect_api_client(port=port) as client,
):
entities, _ = await client.list_entities_services()
keep_light = require_entity(entities, "test_light_keep", LightInfo)
override_light = require_entity(entities, "test_light_override", LightInfo)
# A single subscription serves both initial-state capture and later
# state-change waits -- a second subscribe_states call would restart the
# device's initial-state iterator and could resolve a wait on replayed data.
loop = asyncio.get_running_loop()
state_futures: dict[int, asyncio.Future[Any]] = {}
def on_state(state: Any) -> None:
if state.key in state_futures and not state_futures[state.key].done():
state_futures[state.key].set_result(state)
helper = InitialStateHelper(entities)
client.subscribe_states(helper.on_state_wrapper(on_state))
await helper.wait_for_initial_states()
keep_initial = helper.initial_states[keep_light.key]
assert isinstance(keep_initial, LightState)
assert keep_initial.state is False
override_initial = helper.initial_states[override_light.key]
assert isinstance(override_initial, LightState)
assert override_initial.state is False
# Set both lights to a known, saved state
async def wait_for_state(key: int, timeout: float = 2.0) -> Any:
state_futures[key] = loop.create_future()
try:
return await asyncio.wait_for(state_futures[key], timeout)
finally:
state_futures.pop(key, None)
client.light_command(key=keep_light.key, state=True, brightness=0.64)
await wait_for_state(keep_light.key)
client.light_command(key=override_light.key, state=True, brightness=0.3)
await wait_for_state(override_light.key)
# --- Second run: same binary, same prefs file ---
async with (
run_binary_and_wait_for_port(binary_path, "127.0.0.1", port),
wait_and_connect_api_client(port=port) as client,
):
entities, _ = await client.list_entities_services()
keep_light = require_entity(entities, "test_light_keep", LightInfo)
override_light = require_entity(entities, "test_light_override", LightInfo)
helper = InitialStateHelper(entities)
client.subscribe_states(helper.on_state_wrapper(lambda s: None))
await helper.wait_for_initial_states()
# restore_state: {} -- comes back exactly as saved
keep_state = helper.initial_states[keep_light.key]
assert isinstance(keep_state, LightState)
assert keep_state.state is True
assert keep_state.brightness == pytest.approx(0.64, abs=0.01)
# restore_state: with overrides -- state inverted, brightness forced to 100%
# regardless of what was actually saved (0.3)
override_state = helper.initial_states[override_light.key]
assert isinstance(override_state, LightState)
assert override_state.state is False
assert override_state.brightness == pytest.approx(1.0, abs=0.01)
clear_host_prefs(DEVICE_NAME)