Files
esphome/tests/integration/test_template_climate_set_actions.py
T

115 lines
4.4 KiB
Python

"""Integration test: each settable field forwards its value to the matching set_*_action.
With optimistic: false the entity state stays put until climate.template.publish reports the
device's actual state back, so the actions are the only thing that reacts to a command.
"""
from __future__ import annotations
import asyncio
import aioesphomeapi
from aioesphomeapi import (
ButtonInfo,
ClimateFanMode,
ClimateInfo,
ClimateMode,
ClimatePreset,
ClimateSwingMode,
)
import pytest
from .host_prefs import clear_host_prefs
from .state_utils import InitialStateHelper, require_entity, wait_for_state
from .types import APIClientConnectedFactory, RunCompiledFunction
DEVICE_NAME = "tmpl-clim-set-act"
@pytest.mark.asyncio
async def test_template_climate_set_actions(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Every set_*_action fires with the requested value; state waits for a publish."""
clear_host_prefs(DEVICE_NAME)
log_lines: list[str] = []
def on_log_line(line: str) -> None:
if "_action " in line or "Unsupported" in line:
log_lines.append(line)
def logged(fragment: str) -> bool:
return any(fragment in line for line in log_lines)
async with (
run_compiled(yaml_config, line_callback=on_log_line),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
initial_state_helper = InitialStateHelper(entities)
climate_infos = [e for e in entities if isinstance(e, ClimateInfo)]
assert len(climate_infos) == 1, "Expected exactly 1 climate entity"
test_climate = climate_infos[0]
report_button = require_entity(entities, "report_device_state", ButtonInfo)
unsupported_button = require_entity(
entities, "report_unsupported_mode", ButtonInfo
)
client.subscribe_states(
initial_state_helper.on_state_wrapper(lambda state: None)
)
try:
await initial_state_helper.wait_for_initial_states()
except TimeoutError:
pytest.fail("Timeout waiting for initial states")
# Both traits are derived from the low/high and humidity set actions, not declared.
assert test_climate.supports_two_point_target_temperature
assert test_climate.supports_target_humidity
client.climate_command(test_climate.key, mode=ClimateMode.HEAT)
client.climate_command(
test_climate.key, target_temperature_low=18.0, target_temperature_high=24.0
)
client.climate_command(test_climate.key, target_humidity=55)
client.climate_command(test_climate.key, fan_mode=ClimateFanMode.LOW)
client.climate_command(test_climate.key, custom_fan_mode="turbo")
client.climate_command(test_climate.key, swing_mode=ClimateSwingMode.VERTICAL)
client.climate_command(test_climate.key, preset=ClimatePreset.ECO)
client.climate_command(test_climate.key, custom_preset="eco_plus")
for _ in range(50):
await asyncio.sleep(0.1)
if logged("set_custom_preset_action eco_plus"):
break
assert logged("set_mode_action 3") # CLIMATE_MODE_HEAT
assert logged("set_target_temperature_low_action 18.0")
assert logged("set_target_temperature_high_action 24.0")
assert logged("set_target_humidity_action 55")
assert logged("set_fan_mode_action 3") # CLIMATE_FAN_LOW
assert logged("set_custom_fan_mode_action turbo")
assert logged("set_swing_mode_action 2") # CLIMATE_SWING_VERTICAL
assert logged("set_preset_action 5") # CLIMATE_PRESET_ECO
assert logged("set_custom_preset_action eco_plus")
# optimistic: false, so none of the commands above touched the entity's own state --
# a device report is what actually moves it.
client.button_command(report_button.key)
state = await wait_for_state(
client, lambda s: isinstance(s, aioesphomeapi.ClimateState)
)
assert state.mode == ClimateMode.HEAT
# A publish naming a mode outside supported_modes warns instead of publishing it.
client.button_command(unsupported_button.key)
for _ in range(50):
await asyncio.sleep(0.1)
if logged("Unsupported mode"):
break
assert logged("Unsupported mode")