core/tests/components/zha/test_climate.py

947 lines
31 KiB
Python

"""Test ZHA climate."""
from typing import Literal
from unittest.mock import patch
import pytest
from zha.application.platforms.climate.const import HVAC_MODE_2_SYSTEM, SEQ_OF_OPERATION
import zhaquirks.sinope.thermostat
import zhaquirks.tuya.ts0601_trv
import zigpy.profiles
from zigpy.profiles import zha
import zigpy.types
import zigpy.zcl.clusters
from zigpy.zcl.clusters.hvac import Thermostat
from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE,
ATTR_FAN_MODES,
ATTR_HVAC_ACTION,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
ATTR_PRESET_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
DOMAIN as CLIMATE_DOMAIN,
FAN_AUTO,
FAN_LOW,
FAN_ON,
PRESET_AWAY,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
HVACAction,
HVACMode,
)
from homeassistant.components.zha.helpers import (
ZHAGatewayProxy,
get_zha_gateway,
get_zha_gateway_proxy,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
STATE_UNKNOWN,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from .common import find_entity_id, send_attributes_report
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
CLIMATE = {
1: {
SIG_EP_PROFILE: zha.PROFILE_ID,
SIG_EP_TYPE: zha.DeviceType.THERMOSTAT,
SIG_EP_INPUT: [
zigpy.zcl.clusters.general.Basic.cluster_id,
zigpy.zcl.clusters.general.Identify.cluster_id,
zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
],
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id],
}
}
CLIMATE_FAN = {
1: {
SIG_EP_PROFILE: zha.PROFILE_ID,
SIG_EP_TYPE: zha.DeviceType.THERMOSTAT,
SIG_EP_INPUT: [
zigpy.zcl.clusters.general.Basic.cluster_id,
zigpy.zcl.clusters.general.Identify.cluster_id,
zigpy.zcl.clusters.hvac.Fan.cluster_id,
zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
],
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id],
}
}
CLIMATE_SINOPE = {
1: {
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
SIG_EP_INPUT: [
zigpy.zcl.clusters.general.Basic.cluster_id,
zigpy.zcl.clusters.general.Identify.cluster_id,
zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
65281,
],
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id, 65281],
},
196: {
SIG_EP_PROFILE: 0xC25D,
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
SIG_EP_INPUT: [zigpy.zcl.clusters.general.PowerConfiguration.cluster_id],
SIG_EP_OUTPUT: [],
},
}
MANUF_SINOPE = "Sinope Technologies"
ZCL_ATTR_PLUG = {
"abs_min_heat_setpoint_limit": 800,
"abs_max_heat_setpoint_limit": 3000,
"abs_min_cool_setpoint_limit": 2000,
"abs_max_cool_setpoint_limit": 4000,
"ctrl_sequence_of_oper": Thermostat.ControlSequenceOfOperation.Cooling_and_Heating,
"local_temperature": None,
"max_cool_setpoint_limit": 3900,
"max_heat_setpoint_limit": 2900,
"min_cool_setpoint_limit": 2100,
"min_heat_setpoint_limit": 700,
"occupancy": 1,
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2200,
"pi_cooling_demand": None,
"pi_heating_demand": None,
"running_mode": Thermostat.RunningMode.Off,
"running_state": None,
"system_mode": Thermostat.SystemMode.Off,
"unoccupied_heating_setpoint": 2200,
"unoccupied_cooling_setpoint": 2300,
}
@pytest.fixture(autouse=True)
def climate_platform_only():
"""Only set up the climate and required base platforms to speed up tests."""
with patch(
"homeassistant.components.zha.PLATFORMS",
(
Platform.BUTTON,
Platform.CLIMATE,
Platform.BINARY_SENSOR,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
),
):
yield
@pytest.fixture
def device_climate_mock(hass: HomeAssistant, setup_zha, zigpy_device_mock):
"""Test regular thermostat device."""
async def _dev(clusters, plug=None, manuf=None, quirk=None):
plugged_attrs = ZCL_ATTR_PLUG if plug is None else {**ZCL_ATTR_PLUG, **plug}
zigpy_device = zigpy_device_mock(clusters, manufacturer=manuf, quirk=quirk)
zigpy_device.node_desc.mac_capability_flags |= 0b_0000_0100
zigpy_device.endpoints[1].thermostat.PLUGGED_ATTR_READS = plugged_attrs
await setup_zha()
gateway = get_zha_gateway(hass)
gateway_proxy: ZHAGatewayProxy = get_zha_gateway_proxy(hass)
gateway.get_or_create_device(zigpy_device)
await gateway.async_device_initialized(zigpy_device)
await hass.async_block_till_done(wait_background_tasks=True)
return gateway_proxy.get_device_proxy(zigpy_device.ieee)
return _dev
@pytest.fixture
async def device_climate(device_climate_mock):
"""Plain Climate device."""
return await device_climate_mock(CLIMATE)
@pytest.fixture
async def device_climate_fan(device_climate_mock):
"""Test thermostat with fan device."""
return await device_climate_mock(CLIMATE_FAN)
@pytest.fixture
@patch.object(
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
"ep_attribute",
"sinope_manufacturer_specific",
)
async def device_climate_sinope(device_climate_mock):
"""Sinope thermostat."""
return await device_climate_mock(
CLIMATE_SINOPE,
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
def test_sequence_mappings() -> None:
"""Test correct mapping between control sequence -> HVAC Mode -> Sysmode."""
for hvac_modes in SEQ_OF_OPERATION.values():
for hvac_mode in hvac_modes:
assert hvac_mode in HVAC_MODE_2_SYSTEM
assert Thermostat.SystemMode(HVAC_MODE_2_SYSTEM[hvac_mode]) is not None
async def test_climate_local_temperature(hass: HomeAssistant, device_climate) -> None:
"""Test local temperature."""
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None
await send_attributes_report(hass, thrm_cluster, {0: 2100})
state = hass.states.get(entity_id)
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.0
async def test_climate_hvac_action_running_state(
hass: HomeAssistant, device_climate_sinope
) -> None:
"""Test hvac action via running state."""
thrm_cluster = device_climate_sinope.device.device.endpoints[1].thermostat
entity_id = find_entity_id(Platform.CLIMATE, device_climate_sinope, hass)
sensor_entity_id = find_entity_id(
Platform.SENSOR, device_climate_sinope, hass, "hvac"
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.OFF
await send_attributes_report(
hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.OFF
await send_attributes_report(
hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Auto}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.IDLE
await send_attributes_report(
hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Cool}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.COOLING
await send_attributes_report(
hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Heat}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.HEATING
await send_attributes_report(
hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.IDLE
await send_attributes_report(
hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_State_On}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.FAN
hvac_sensor_state = hass.states.get(sensor_entity_id)
assert hvac_sensor_state.state == HVACAction.FAN
async def test_climate_hvac_action_pi_demand(
hass: HomeAssistant, device_climate
) -> None:
"""Test hvac action based on pi_heating/cooling_demand attrs."""
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
state = hass.states.get(entity_id)
assert ATTR_HVAC_ACTION not in state.attributes
await send_attributes_report(hass, thrm_cluster, {0x0007: 10})
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING
await send_attributes_report(hass, thrm_cluster, {0x0008: 20})
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
await send_attributes_report(hass, thrm_cluster, {0x0007: 0})
await send_attributes_report(hass, thrm_cluster, {0x0008: 0})
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF
await send_attributes_report(
hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Heat}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
await send_attributes_report(
hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Cool}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
@pytest.mark.parametrize(
("sys_mode", "hvac_mode"),
[
(Thermostat.SystemMode.Auto, HVACMode.HEAT_COOL),
(Thermostat.SystemMode.Cool, HVACMode.COOL),
(Thermostat.SystemMode.Heat, HVACMode.HEAT),
(Thermostat.SystemMode.Pre_cooling, HVACMode.COOL),
(Thermostat.SystemMode.Fan_only, HVACMode.FAN_ONLY),
(Thermostat.SystemMode.Dry, HVACMode.DRY),
],
)
async def test_hvac_mode(
hass: HomeAssistant, device_climate, sys_mode, hvac_mode
) -> None:
"""Test HVAC mode."""
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
await send_attributes_report(hass, thrm_cluster, {0x001C: sys_mode})
state = hass.states.get(entity_id)
assert state.state == hvac_mode
await send_attributes_report(
hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Off}
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
await send_attributes_report(hass, thrm_cluster, {0x001C: 0xFF})
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN
@pytest.mark.parametrize(
("seq_of_op", "modes"),
[
(0xFF, {HVACMode.OFF}),
(0x00, {HVACMode.OFF, HVACMode.COOL}),
(0x01, {HVACMode.OFF, HVACMode.COOL}),
(0x02, {HVACMode.OFF, HVACMode.HEAT}),
(0x03, {HVACMode.OFF, HVACMode.HEAT}),
(0x04, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}),
(0x05, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}),
],
)
async def test_hvac_modes(
hass: HomeAssistant, device_climate_mock, seq_of_op, modes
) -> None:
"""Test HVAC modes from sequence of operations."""
device_climate = await device_climate_mock(
CLIMATE, {"ctrl_sequence_of_oper": seq_of_op}
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
state = hass.states.get(entity_id)
assert set(state.attributes[ATTR_HVAC_MODES]) == modes
@pytest.mark.parametrize(
("sys_mode", "preset", "target_temp"),
[
(Thermostat.SystemMode.Heat, None, 22),
(Thermostat.SystemMode.Heat, PRESET_AWAY, 16),
(Thermostat.SystemMode.Cool, None, 25),
(Thermostat.SystemMode.Cool, PRESET_AWAY, 27),
],
)
async def test_target_temperature(
hass: HomeAssistant,
device_climate_mock,
sys_mode: Thermostat.SystemMode,
preset: Literal[PRESET_AWAY] | None,
target_temp: int,
) -> None:
"""Test target temperature property."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2200,
"system_mode": sys_mode,
"unoccupied_heating_setpoint": 1600,
"unoccupied_cooling_setpoint": 2700,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
if preset:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TEMPERATURE] == target_temp
@pytest.mark.parametrize(
("preset", "unoccupied", "target_temp"),
[
(None, 1800, 17),
(PRESET_AWAY, 1800, 18),
(PRESET_AWAY, None, None),
],
)
async def test_target_temperature_high(
hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp
) -> None:
"""Test target temperature high property."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 1700,
"system_mode": Thermostat.SystemMode.Auto,
"unoccupied_cooling_setpoint": unoccupied,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
if preset:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_HIGH] == target_temp
@pytest.mark.parametrize(
("preset", "unoccupied", "target_temp"),
[
(None, 1600, 21),
(PRESET_AWAY, 1600, 16),
(PRESET_AWAY, None, None),
],
)
async def test_target_temperature_low(
hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp
) -> None:
"""Test target temperature low property."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_heating_setpoint": 2100,
"system_mode": Thermostat.SystemMode.Auto,
"unoccupied_heating_setpoint": unoccupied,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
if preset:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] == target_temp
@pytest.mark.parametrize(
("hvac_mode", "sys_mode"),
[
(HVACMode.AUTO, None),
(HVACMode.COOL, Thermostat.SystemMode.Cool),
(HVACMode.DRY, None),
(HVACMode.FAN_ONLY, None),
(HVACMode.HEAT, Thermostat.SystemMode.Heat),
(HVACMode.HEAT_COOL, Thermostat.SystemMode.Auto),
],
)
async def test_set_hvac_mode(
hass: HomeAssistant, device_climate, hvac_mode, sys_mode
) -> None:
"""Test setting hvac mode."""
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode},
blocking=True,
)
state = hass.states.get(entity_id)
if sys_mode is not None:
assert state.state == hvac_mode
assert thrm_cluster.write_attributes.call_count == 1
assert thrm_cluster.write_attributes.call_args[0][0] == {
"system_mode": sys_mode
}
else:
assert thrm_cluster.write_attributes.call_count == 0
assert state.state == HVACMode.OFF
# turn off
thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVACMode.OFF},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
assert thrm_cluster.write_attributes.call_count == 1
assert thrm_cluster.write_attributes.call_args[0][0] == {
"system_mode": Thermostat.SystemMode.Off
}
async def test_set_temperature_hvac_mode(hass: HomeAssistant, device_climate) -> None:
"""Test setting HVAC mode in temperature service call."""
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert state.state == HVACMode.OFF
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_HVAC_MODE: HVACMode.HEAT_COOL,
ATTR_TEMPERATURE: 20,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == HVACMode.HEAT_COOL
assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args[0][0] == {
"system_mode": Thermostat.SystemMode.Auto
}
async def test_set_temperature_heat_cool(
hass: HomeAssistant, device_climate_mock
) -> None:
"""Test setting temperature service call in heating/cooling HVAC mode."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2000,
"system_mode": Thermostat.SystemMode.Auto,
"unoccupied_heating_setpoint": 1600,
"unoccupied_cooling_setpoint": 2700,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert state.state == HVACMode.HEAT_COOL
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] == 20.0
assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.0
assert thrm_cluster.write_attributes.await_count == 0
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TARGET_TEMP_HIGH: 26,
ATTR_TARGET_TEMP_LOW: 19,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] == 19.0
assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 26.0
assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"occupied_heating_setpoint": 1900
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"occupied_cooling_setpoint": 2600
}
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
blocking=True,
)
thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TARGET_TEMP_HIGH: 30,
ATTR_TARGET_TEMP_LOW: 15,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] == 15.0
assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 30.0
assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"unoccupied_heating_setpoint": 1500
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"unoccupied_cooling_setpoint": 3000
}
async def test_set_temperature_heat(hass: HomeAssistant, device_climate_mock) -> None:
"""Test setting temperature service call in heating HVAC mode."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2000,
"system_mode": Thermostat.SystemMode.Heat,
"unoccupied_heating_setpoint": 1600,
"unoccupied_cooling_setpoint": 2700,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert state.state == HVACMode.HEAT
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TARGET_TEMP_HIGH: 30,
ATTR_TARGET_TEMP_LOW: 15,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 20.0
assert thrm_cluster.write_attributes.await_count == 0
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 21.0
assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"occupied_heating_setpoint": 2100
}
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
blocking=True,
)
thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 22.0
assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"unoccupied_heating_setpoint": 2200
}
async def test_set_temperature_cool(hass: HomeAssistant, device_climate_mock) -> None:
"""Test setting temperature service call in cooling HVAC mode."""
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2000,
"system_mode": Thermostat.SystemMode.Cool,
"unoccupied_cooling_setpoint": 1600,
"unoccupied_heating_setpoint": 2700,
},
manuf=MANUF_SINOPE,
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert state.state == HVACMode.COOL
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TARGET_TEMP_HIGH: 30,
ATTR_TARGET_TEMP_LOW: 15,
},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 25.0
assert thrm_cluster.write_attributes.await_count == 0
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 21.0
assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"occupied_cooling_setpoint": 2100
}
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
blocking=True,
)
thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] == 22.0
assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"unoccupied_cooling_setpoint": 2200
}
async def test_set_temperature_wrong_mode(
hass: HomeAssistant, device_climate_mock
) -> None:
"""Test setting temperature service call for wrong HVAC mode."""
with patch.object(
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
"ep_attribute",
"sinope_manufacturer_specific",
):
device_climate = await device_climate_mock(
CLIMATE_SINOPE,
{
"occupied_cooling_setpoint": 2500,
"occupied_heating_setpoint": 2000,
"system_mode": Thermostat.SystemMode.Dry,
"unoccupied_cooling_setpoint": 1600,
"unoccupied_heating_setpoint": 2700,
},
manuf=MANUF_SINOPE,
)
entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
thrm_cluster = device_climate.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert state.state == HVACMode.DRY
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 24},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
assert state.attributes[ATTR_TEMPERATURE] is None
assert thrm_cluster.write_attributes.await_count == 0
async def test_fan_mode(hass: HomeAssistant, device_climate_fan) -> None:
"""Test fan mode."""
entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
thrm_cluster = device_climate_fan.device.device.endpoints[1].thermostat
state = hass.states.get(entity_id)
assert set(state.attributes[ATTR_FAN_MODES]) == {FAN_AUTO, FAN_ON}
assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO
await send_attributes_report(
hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_State_On}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_FAN_MODE] == FAN_ON
await send_attributes_report(
hass, thrm_cluster, {"running_state": Thermostat.RunningState.Idle}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO
await send_attributes_report(
hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_2nd_Stage_On}
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_FAN_MODE] == FAN_ON
async def test_set_fan_mode_not_supported(
hass: HomeAssistant, device_climate_fan
) -> None:
"""Test fan setting unsupported mode."""
entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
fan_cluster = device_climate_fan.device.device.endpoints[1].fan
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
blocking=True,
)
assert fan_cluster.write_attributes.await_count == 0
async def test_set_fan_mode(hass: HomeAssistant, device_climate_fan) -> None:
"""Test fan mode setting."""
entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
fan_cluster = device_climate_fan.device.device.endpoints[1].fan
state = hass.states.get(entity_id)
assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON},
blocking=True,
)
assert fan_cluster.write_attributes.await_count == 1
assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 4}
fan_cluster.write_attributes.reset_mock()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_AUTO},
blocking=True,
)
assert fan_cluster.write_attributes.await_count == 1
assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5}