mirror of
https://github.com/esphome/esphome.git
synced 2026-09-12 15:57:33 +00:00
115 lines
4.4 KiB
Python
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")
|