487 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
"""Test ESPHome climates."""
 | 
						|
 | 
						|
import math
 | 
						|
from unittest.mock import call
 | 
						|
 | 
						|
from aioesphomeapi import (
 | 
						|
    APIClient,
 | 
						|
    ClimateAction,
 | 
						|
    ClimateFanMode,
 | 
						|
    ClimateInfo,
 | 
						|
    ClimateMode,
 | 
						|
    ClimatePreset,
 | 
						|
    ClimateState,
 | 
						|
    ClimateSwingMode,
 | 
						|
)
 | 
						|
import pytest
 | 
						|
from syrupy import SnapshotAssertion
 | 
						|
 | 
						|
from homeassistant.components.climate import (
 | 
						|
    ATTR_CURRENT_HUMIDITY,
 | 
						|
    ATTR_CURRENT_TEMPERATURE,
 | 
						|
    ATTR_FAN_MODE,
 | 
						|
    ATTR_HUMIDITY,
 | 
						|
    ATTR_HVAC_MODE,
 | 
						|
    ATTR_MAX_HUMIDITY,
 | 
						|
    ATTR_MIN_HUMIDITY,
 | 
						|
    ATTR_PRESET_MODE,
 | 
						|
    ATTR_SWING_MODE,
 | 
						|
    ATTR_TARGET_TEMP_HIGH,
 | 
						|
    ATTR_TARGET_TEMP_LOW,
 | 
						|
    ATTR_TEMPERATURE,
 | 
						|
    DOMAIN as CLIMATE_DOMAIN,
 | 
						|
    FAN_HIGH,
 | 
						|
    SERVICE_SET_FAN_MODE,
 | 
						|
    SERVICE_SET_HUMIDITY,
 | 
						|
    SERVICE_SET_HVAC_MODE,
 | 
						|
    SERVICE_SET_PRESET_MODE,
 | 
						|
    SERVICE_SET_SWING_MODE,
 | 
						|
    SERVICE_SET_TEMPERATURE,
 | 
						|
    SWING_BOTH,
 | 
						|
    HVACMode,
 | 
						|
)
 | 
						|
from homeassistant.const import ATTR_ENTITY_ID
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.exceptions import ServiceValidationError
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic climate entity."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            supports_action=True,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.COOL,
 | 
						|
            action=ClimateAction.COOLING,
 | 
						|
            current_temperature=30,
 | 
						|
            target_temperature=20,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.COOL
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_TEMPERATURE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)])
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity_with_step_and_two_point(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic climate entity."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            supports_two_point_target_temperature=True,
 | 
						|
            visual_target_temperature_step=2,
 | 
						|
            visual_current_temperature_step=2,
 | 
						|
            supports_action=False,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
            supported_modes=[ClimateMode.COOL, ClimateMode.HEAT, ClimateMode.AUTO],
 | 
						|
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.COOL,
 | 
						|
            current_temperature=30,
 | 
						|
            target_temperature=20,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.COOL
 | 
						|
 | 
						|
    with pytest.raises(ServiceValidationError):
 | 
						|
        await hass.services.async_call(
 | 
						|
            CLIMATE_DOMAIN,
 | 
						|
            SERVICE_SET_TEMPERATURE,
 | 
						|
            {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25},
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_TEMPERATURE,
 | 
						|
        {
 | 
						|
            ATTR_ENTITY_ID: "climate.test_myclimate",
 | 
						|
            ATTR_HVAC_MODE: HVACMode.AUTO,
 | 
						|
            ATTR_TARGET_TEMP_LOW: 20,
 | 
						|
            ATTR_TARGET_TEMP_HIGH: 30,
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [
 | 
						|
            call(
 | 
						|
                key=1,
 | 
						|
                mode=ClimateMode.AUTO,
 | 
						|
                target_temperature_low=20.0,
 | 
						|
                target_temperature_high=30.0,
 | 
						|
            )
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity_with_step_and_target_temp(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic climate entity."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            visual_target_temperature_step=2,
 | 
						|
            visual_current_temperature_step=2,
 | 
						|
            supports_action=True,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
            supported_fan_modes=[ClimateFanMode.LOW, ClimateFanMode.HIGH],
 | 
						|
            supported_modes=[ClimateMode.COOL, ClimateMode.HEAT, ClimateMode.AUTO],
 | 
						|
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
 | 
						|
            supported_custom_presets=["preset1", "preset2"],
 | 
						|
            supported_custom_fan_modes=["fan1", "fan2"],
 | 
						|
            supported_swing_modes=[ClimateSwingMode.BOTH, ClimateSwingMode.OFF],
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.COOL,
 | 
						|
            action=ClimateAction.COOLING,
 | 
						|
            current_temperature=30,
 | 
						|
            target_temperature=20,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.COOL
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_TEMPERATURE,
 | 
						|
        {
 | 
						|
            ATTR_ENTITY_ID: "climate.test_myclimate",
 | 
						|
            ATTR_HVAC_MODE: HVACMode.AUTO,
 | 
						|
            ATTR_TEMPERATURE: 25,
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [call(key=1, mode=ClimateMode.AUTO, target_temperature=25.0)]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    with pytest.raises(ServiceValidationError):
 | 
						|
        await hass.services.async_call(
 | 
						|
            CLIMATE_DOMAIN,
 | 
						|
            SERVICE_SET_TEMPERATURE,
 | 
						|
            {
 | 
						|
                ATTR_ENTITY_ID: "climate.test_myclimate",
 | 
						|
                ATTR_HVAC_MODE: HVACMode.AUTO,
 | 
						|
                ATTR_TARGET_TEMP_LOW: 20,
 | 
						|
                ATTR_TARGET_TEMP_HIGH: 30,
 | 
						|
            },
 | 
						|
            blocking=True,
 | 
						|
        )
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_HVAC_MODE,
 | 
						|
        {
 | 
						|
            ATTR_ENTITY_ID: "climate.test_myclimate",
 | 
						|
            ATTR_HVAC_MODE: HVACMode.HEAT,
 | 
						|
        },
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [
 | 
						|
            call(
 | 
						|
                key=1,
 | 
						|
                mode=ClimateMode.HEAT,
 | 
						|
            )
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_PRESET_MODE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_PRESET_MODE: "away"},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [
 | 
						|
            call(
 | 
						|
                key=1,
 | 
						|
                preset=ClimatePreset.AWAY,
 | 
						|
            )
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_PRESET_MODE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_PRESET_MODE: "preset1"},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls([call(key=1, custom_preset="preset1")])
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_FAN_MODE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_FAN_MODE: FAN_HIGH},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [call(key=1, fan_mode=ClimateFanMode.HIGH)]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_FAN_MODE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_FAN_MODE: "fan2"},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls([call(key=1, custom_fan_mode="fan2")])
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_SWING_MODE,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_SWING_MODE: SWING_BOTH},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls(
 | 
						|
        [call(key=1, swing_mode=ClimateSwingMode.BOTH)]
 | 
						|
    )
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity_with_humidity(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic climate entity with humidity."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            supports_two_point_target_temperature=True,
 | 
						|
            supports_action=True,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
            supports_current_humidity=True,
 | 
						|
            supports_target_humidity=True,
 | 
						|
            visual_min_humidity=10.1,
 | 
						|
            visual_max_humidity=29.7,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.AUTO,
 | 
						|
            action=ClimateAction.COOLING,
 | 
						|
            current_temperature=30,
 | 
						|
            target_temperature=20,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
            current_humidity=20.1,
 | 
						|
            target_humidity=25.7,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.AUTO
 | 
						|
    attributes = state.attributes
 | 
						|
    assert attributes[ATTR_CURRENT_HUMIDITY] == 20
 | 
						|
    assert attributes[ATTR_HUMIDITY] == 26
 | 
						|
    assert attributes[ATTR_MAX_HUMIDITY] == 30
 | 
						|
    assert attributes[ATTR_MIN_HUMIDITY] == 10
 | 
						|
 | 
						|
    await hass.services.async_call(
 | 
						|
        CLIMATE_DOMAIN,
 | 
						|
        SERVICE_SET_HUMIDITY,
 | 
						|
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_HUMIDITY: 23},
 | 
						|
        blocking=True,
 | 
						|
    )
 | 
						|
    mock_client.climate_command.assert_has_calls([call(key=1, target_humidity=23)])
 | 
						|
    mock_client.climate_command.reset_mock()
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity_with_inf_value(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic climate entity with infinite temp."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            supports_two_point_target_temperature=True,
 | 
						|
            supports_action=True,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
            supports_current_humidity=True,
 | 
						|
            supports_target_humidity=True,
 | 
						|
            visual_min_humidity=10.1,
 | 
						|
            visual_max_humidity=29.7,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.AUTO,
 | 
						|
            action=ClimateAction.COOLING,
 | 
						|
            current_temperature=math.inf,
 | 
						|
            target_temperature=math.inf,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
            current_humidity=20.1,
 | 
						|
            target_humidity=25.7,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.AUTO
 | 
						|
    attributes = state.attributes
 | 
						|
    assert attributes[ATTR_CURRENT_HUMIDITY] == 20
 | 
						|
    assert attributes[ATTR_HUMIDITY] == 26
 | 
						|
    assert attributes[ATTR_MAX_HUMIDITY] == 30
 | 
						|
    assert attributes[ATTR_MIN_HUMIDITY] == 10
 | 
						|
    assert ATTR_TEMPERATURE not in attributes
 | 
						|
    assert attributes[ATTR_CURRENT_TEMPERATURE] is None
 | 
						|
 | 
						|
 | 
						|
async def test_climate_entity_attributes(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_client: APIClient,
 | 
						|
    mock_generic_device_entry,
 | 
						|
    snapshot: SnapshotAssertion,
 | 
						|
) -> None:
 | 
						|
    """Test a climate entity sets correct attributes."""
 | 
						|
    entity_info = [
 | 
						|
        ClimateInfo(
 | 
						|
            object_id="myclimate",
 | 
						|
            key=1,
 | 
						|
            name="my climate",
 | 
						|
            unique_id="my_climate",
 | 
						|
            supports_current_temperature=True,
 | 
						|
            visual_target_temperature_step=2,
 | 
						|
            visual_current_temperature_step=2,
 | 
						|
            supports_action=True,
 | 
						|
            visual_min_temperature=10.0,
 | 
						|
            visual_max_temperature=30.0,
 | 
						|
            supported_fan_modes=[ClimateFanMode.LOW, ClimateFanMode.HIGH],
 | 
						|
            supported_modes=[
 | 
						|
                ClimateMode.COOL,
 | 
						|
                ClimateMode.HEAT,
 | 
						|
                ClimateMode.AUTO,
 | 
						|
                ClimateMode.OFF,
 | 
						|
            ],
 | 
						|
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
 | 
						|
            supported_custom_presets=["preset1", "preset2"],
 | 
						|
            supported_custom_fan_modes=["fan1", "fan2"],
 | 
						|
            supported_swing_modes=[ClimateSwingMode.BOTH, ClimateSwingMode.OFF],
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [
 | 
						|
        ClimateState(
 | 
						|
            key=1,
 | 
						|
            mode=ClimateMode.COOL,
 | 
						|
            action=ClimateAction.COOLING,
 | 
						|
            current_temperature=30,
 | 
						|
            target_temperature=20,
 | 
						|
            fan_mode=ClimateFanMode.AUTO,
 | 
						|
            swing_mode=ClimateSwingMode.BOTH,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    user_service = []
 | 
						|
    await mock_generic_device_entry(
 | 
						|
        mock_client=mock_client,
 | 
						|
        entity_info=entity_info,
 | 
						|
        user_service=user_service,
 | 
						|
        states=states,
 | 
						|
    )
 | 
						|
    state = hass.states.get("climate.test_myclimate")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == HVACMode.COOL
 | 
						|
    assert state.attributes == snapshot(name="climate-entity-attributes")
 |