1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-23 20:23:50 +01:00

[climate] Add some integration tests (#11439)

This commit is contained in:
Keith Burzinski
2025-10-21 02:26:18 -05:00
committed by GitHub
parent 0ae9009e41
commit 66afe4a9be
6 changed files with 423 additions and 1 deletions

View File

@@ -0,0 +1,112 @@
esphome:
name: host-climate-test
host:
api:
logger:
climate:
- platform: thermostat
id: dual_mode_thermostat
name: Dual-mode Thermostat
sensor: host_thermostat_temperature_sensor
humidity_sensor: host_thermostat_humidity_sensor
humidity_hysteresis: 1.0
min_cooling_off_time: 20s
min_cooling_run_time: 20s
max_cooling_run_time: 30s
supplemental_cooling_delta: 3.0
min_heating_off_time: 20s
min_heating_run_time: 20s
max_heating_run_time: 30s
supplemental_heating_delta: 3.0
min_fanning_off_time: 20s
min_fanning_run_time: 20s
min_idle_time: 10s
visual:
min_humidity: 20%
max_humidity: 70%
min_temperature: 15.0
max_temperature: 32.0
temperature_step: 0.1
default_preset: home
preset:
- name: "away"
default_target_temperature_low: 18.0
default_target_temperature_high: 24.0
- name: "home"
default_target_temperature_low: 18.0
default_target_temperature_high: 24.0
auto_mode:
- logger.log: "AUTO mode set"
heat_cool_mode:
- logger.log: "HEAT_COOL mode set"
cool_action:
- switch.turn_on: air_cond
supplemental_cooling_action:
- switch.turn_on: air_cond_2
heat_action:
- switch.turn_on: heater
supplemental_heating_action:
- switch.turn_on: heater_2
dry_action:
- switch.turn_on: air_cond
fan_only_action:
- switch.turn_on: fan_only
idle_action:
- switch.turn_off: air_cond
- switch.turn_off: air_cond_2
- switch.turn_off: heater
- switch.turn_off: heater_2
- switch.turn_off: fan_only
humidity_control_humidify_action:
- switch.turn_on: humidifier
humidity_control_off_action:
- switch.turn_off: humidifier
sensor:
- platform: template
id: host_thermostat_humidity_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 42.0;
update_interval: 0.1s
- platform: template
id: host_thermostat_temperature_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 22.0;
update_interval: 0.1s
switch:
- platform: template
id: air_cond
name: Air Conditioner
optimistic: true
- platform: template
id: air_cond_2
name: Air Conditioner 2
optimistic: true
- platform: template
id: fan_only
name: Fan
optimistic: true
- platform: template
id: heater
name: Heater
optimistic: true
- platform: template
id: heater_2
name: Heater 2
optimistic: true
- platform: template
id: dehumidifier
name: Dehumidifier
optimistic: true
- platform: template
id: humidifier
name: Humidifier
optimistic: true

View File

@@ -0,0 +1,108 @@
esphome:
name: host-climate-test
host:
api:
logger:
climate:
- platform: thermostat
id: dual_mode_thermostat
name: Dual-mode Thermostat
sensor: host_thermostat_temperature_sensor
humidity_sensor: host_thermostat_humidity_sensor
humidity_hysteresis: 1.0
min_cooling_off_time: 20s
min_cooling_run_time: 20s
max_cooling_run_time: 30s
supplemental_cooling_delta: 3.0
min_heating_off_time: 20s
min_heating_run_time: 20s
max_heating_run_time: 30s
supplemental_heating_delta: 3.0
min_fanning_off_time: 20s
min_fanning_run_time: 20s
min_idle_time: 10s
visual:
min_humidity: 20%
max_humidity: 70%
min_temperature: 15.0
max_temperature: 32.0
temperature_step: 0.1
default_preset: home
preset:
- name: "away"
default_target_temperature_low: 18.0
default_target_temperature_high: 24.0
- name: "home"
default_target_temperature_low: 18.0
default_target_temperature_high: 24.0
auto_mode:
- logger.log: "AUTO mode set"
heat_cool_mode:
- logger.log: "HEAT_COOL mode set"
cool_action:
- switch.turn_on: air_cond
supplemental_cooling_action:
- switch.turn_on: air_cond_2
heat_action:
- switch.turn_on: heater
supplemental_heating_action:
- switch.turn_on: heater_2
dry_action:
- switch.turn_on: air_cond
fan_only_action:
- switch.turn_on: fan_only
idle_action:
- switch.turn_off: air_cond
- switch.turn_off: air_cond_2
- switch.turn_off: heater
- switch.turn_off: heater_2
- switch.turn_off: fan_only
humidity_control_humidify_action:
- switch.turn_on: humidifier
humidity_control_off_action:
- switch.turn_off: humidifier
sensor:
- platform: template
id: host_thermostat_humidity_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 42.0;
update_interval: 0.1s
- platform: template
id: host_thermostat_temperature_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 22.0;
update_interval: 0.1s
switch:
- platform: template
id: air_cond
name: Air Conditioner
optimistic: true
- platform: template
id: air_cond_2
name: Air Conditioner 2
optimistic: true
- platform: template
id: fan_only
name: Fan
optimistic: true
- platform: template
id: heater
name: Heater
optimistic: true
- platform: template
id: heater_2
name: Heater 2
optimistic: true
- platform: template
id: humidifier
name: Humidifier
optimistic: true

View File

@@ -210,7 +210,15 @@ sensor:
name: "Test Sensor 50" name: "Test Sensor 50"
lambda: return 50.0; lambda: return 50.0;
update_interval: 0.1s update_interval: 0.1s
# Temperature sensor for the thermostat # Sensors for the thermostat
- platform: template
name: "Humidity Sensor"
id: humidity_sensor
lambda: return 35.0;
unit_of_measurement: "%"
device_class: humidity
state_class: measurement
update_interval: 5s
- platform: template - platform: template
name: "Temperature Sensor" name: "Temperature Sensor"
id: temp_sensor id: temp_sensor
@@ -295,6 +303,11 @@ valve:
- logger.log: "Valve stopping" - logger.log: "Valve stopping"
output: output:
- platform: template
id: humidifier_output
type: binary
write_action:
- logger.log: "Humidifier output changed"
- platform: template - platform: template
id: heater_output id: heater_output
type: binary type: binary
@@ -305,18 +318,31 @@ output:
type: binary type: binary
write_action: write_action:
- logger.log: "Cooler output changed" - logger.log: "Cooler output changed"
- platform: template
id: fan_output
type: binary
write_action:
- logger.log: "Fan output changed"
climate: climate:
- platform: thermostat - platform: thermostat
name: "Test Thermostat" name: "Test Thermostat"
sensor: temp_sensor sensor: temp_sensor
humidity_sensor: humidity_sensor
default_preset: Home default_preset: Home
on_boot_restore_from: default_preset on_boot_restore_from: default_preset
min_heating_off_time: 1s min_heating_off_time: 1s
min_heating_run_time: 1s min_heating_run_time: 1s
min_cooling_off_time: 1s min_cooling_off_time: 1s
min_cooling_run_time: 1s min_cooling_run_time: 1s
min_fan_mode_switching_time: 1s
min_idle_time: 1s min_idle_time: 1s
visual:
min_humidity: 20%
max_humidity: 70%
min_temperature: 15.0
max_temperature: 32.0
temperature_step: 0.1
heat_action: heat_action:
- output.turn_on: heater_output - output.turn_on: heater_output
cool_action: cool_action:
@@ -324,6 +350,14 @@ climate:
idle_action: idle_action:
- output.turn_off: heater_output - output.turn_off: heater_output
- output.turn_off: cooler_output - output.turn_off: cooler_output
humidity_control_humidify_action:
- output.turn_on: humidifier_output
humidity_control_off_action:
- output.turn_off: humidifier_output
fan_mode_auto_action:
- output.turn_off: fan_output
fan_mode_on_action:
- output.turn_on: fan_output
preset: preset:
- name: Home - name: Home
default_target_temperature_low: 20 default_target_temperature_low: 20

View File

@@ -0,0 +1,49 @@
"""Integration test for Host mode with climate."""
from __future__ import annotations
import asyncio
import aioesphomeapi
from aioesphomeapi import ClimateAction, ClimateMode, ClimatePreset, EntityState
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_host_mode_climate_basic_state(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test basic climate state reporting."""
loop = asyncio.get_running_loop()
async with run_compiled(yaml_config), api_client_connected() as client:
states: dict[int, EntityState] = {}
climate_future: asyncio.Future[EntityState] = loop.create_future()
def on_state(state: EntityState) -> None:
states[state.key] = state
if (
isinstance(state, aioesphomeapi.ClimateState)
and not climate_future.done()
):
climate_future.set_result(state)
client.subscribe_states(on_state)
try:
climate_state = await asyncio.wait_for(climate_future, timeout=5.0)
except TimeoutError:
pytest.fail("Climate state not received within 5 seconds")
assert isinstance(climate_state, aioesphomeapi.ClimateState)
assert climate_state.mode == ClimateMode.OFF
assert climate_state.action == ClimateAction.OFF
assert climate_state.current_temperature == 22.0
assert climate_state.target_temperature_low == 18.0
assert climate_state.target_temperature_high == 24.0
assert climate_state.preset == ClimatePreset.HOME
assert climate_state.current_humidity == 42.0
assert climate_state.target_humidity == 20.0

View File

@@ -0,0 +1,76 @@
"""Integration test for Host mode with climate."""
from __future__ import annotations
import asyncio
import aioesphomeapi
from aioesphomeapi import ClimateInfo, ClimateMode, EntityState
import pytest
from .state_utils import InitialStateHelper
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_host_mode_climate_control(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test climate mode control."""
loop = asyncio.get_running_loop()
async with run_compiled(yaml_config), api_client_connected() as client:
states: dict[int, EntityState] = {}
climate_future: asyncio.Future[EntityState] = loop.create_future()
def on_state(state: EntityState) -> None:
states[state.key] = state
if (
isinstance(state, aioesphomeapi.ClimateState)
and state.mode == ClimateMode.HEAT
and state.target_temperature_low == 21.5
and state.target_temperature_high == 26.5
and not climate_future.done()
):
climate_future.set_result(state)
# Get entities and set up state synchronization
entities, services = 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 at least 1 climate entity"
# Subscribe with the wrapper that filters initial states
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
# Wait for all initial states to be broadcast
try:
await initial_state_helper.wait_for_initial_states()
except TimeoutError:
pytest.fail("Timeout waiting for initial states")
test_climate = next(
(c for c in climate_infos if c.name == "Dual-mode Thermostat"), None
)
assert test_climate is not None, (
"Dual-mode Thermostat thermostat climate not found"
)
# Adjust setpoints
client.climate_command(
test_climate.key,
mode=ClimateMode.HEAT,
target_temperature_low=21.5,
target_temperature_high=26.5,
)
try:
climate_state = await asyncio.wait_for(climate_future, timeout=5.0)
except TimeoutError:
pytest.fail("Climate state not received within 5 seconds")
assert isinstance(climate_state, aioesphomeapi.ClimateState)
assert climate_state.mode == ClimateMode.HEAT
assert climate_state.target_temperature_low == 21.5
assert climate_state.target_temperature_high == 26.5

View File

@@ -5,7 +5,10 @@ from __future__ import annotations
import asyncio import asyncio
from aioesphomeapi import ( from aioesphomeapi import (
ClimateFanMode,
ClimateFeature,
ClimateInfo, ClimateInfo,
ClimateMode,
DateInfo, DateInfo,
DateState, DateState,
DateTimeInfo, DateTimeInfo,
@@ -121,6 +124,46 @@ async def test_host_mode_many_entities(
assert len(climate_infos) >= 1, "Expected at least 1 climate entity" assert len(climate_infos) >= 1, "Expected at least 1 climate entity"
climate_info = climate_infos[0] climate_info = climate_infos[0]
# Verify feature flags set as expected
assert climate_info.feature_flags == (
ClimateFeature.SUPPORTS_ACTION
| ClimateFeature.SUPPORTS_CURRENT_HUMIDITY
| ClimateFeature.SUPPORTS_CURRENT_TEMPERATURE
| ClimateFeature.SUPPORTS_TWO_POINT_TARGET_TEMPERATURE
| ClimateFeature.SUPPORTS_TARGET_HUMIDITY
)
# Verify modes
assert climate_info.supported_modes == [
ClimateMode.OFF,
ClimateMode.COOL,
ClimateMode.HEAT,
], f"Expected modes [OFF, COOL, HEAT], got {climate_info.supported_modes}"
# Verify visual parameters
assert climate_info.visual_min_temperature == 15.0, (
f"Expected min_temperature=15.0, got {climate_info.visual_min_temperature}"
)
assert climate_info.visual_max_temperature == 32.0, (
f"Expected max_temperature=32.0, got {climate_info.visual_max_temperature}"
)
assert climate_info.visual_target_temperature_step == 0.1, (
f"Expected temperature_step=0.1, got {climate_info.visual_target_temperature_step}"
)
assert climate_info.visual_min_humidity == 20.0, (
f"Expected min_humidity=20.0, got {climate_info.visual_min_humidity}"
)
assert climate_info.visual_max_humidity == 70.0, (
f"Expected max_humidity=70.0, got {climate_info.visual_max_humidity}"
)
# Verify fan modes
assert climate_info.supported_fan_modes == [
ClimateFanMode.ON,
ClimateFanMode.AUTO,
], f"Expected fan modes [ON, AUTO], got {climate_info.supported_fan_modes}"
# Verify the thermostat has presets # Verify the thermostat has presets
assert len(climate_info.supported_presets) > 0, ( assert len(climate_info.supported_presets) > 0, (
"Expected climate to have presets" "Expected climate to have presets"