Compare commits

..

1 Commits

Author SHA1 Message Date
Erik
6f9c6a1864 Add climate conditions 2026-01-15 20:31:27 +01:00
10 changed files with 561 additions and 54 deletions

View File

@@ -124,6 +124,7 @@ NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG = "new_triggers_conditions"
_EXPERIMENTAL_CONDITION_PLATFORMS = {
"alarm_control_panel",
"climate",
"fan",
"light",
}

View File

@@ -0,0 +1,39 @@
"""Provides conditions for climates."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import (
Condition,
make_entity_state_attribute_condition,
make_entity_state_condition,
)
from .const import ATTR_HVAC_ACTION, DOMAIN, HVACAction, HVACMode
CONDITIONS: dict[str, type[Condition]] = {
"is_off": make_entity_state_condition(DOMAIN, HVACMode.OFF),
"is_on": make_entity_state_condition(
DOMAIN,
{
HVACMode.AUTO,
HVACMode.COOL,
HVACMode.DRY,
HVACMode.FAN_ONLY,
HVACMode.HEAT,
HVACMode.HEAT_COOL,
},
),
"is_cooling": make_entity_state_attribute_condition(
DOMAIN, ATTR_HVAC_ACTION, HVACAction.COOLING
),
"is_drying": make_entity_state_attribute_condition(
DOMAIN, ATTR_HVAC_ACTION, HVACAction.DRYING
),
"is_heating": make_entity_state_attribute_condition(
DOMAIN, ATTR_HVAC_ACTION, HVACAction.HEATING
),
}
async def async_get_conditions(hass: HomeAssistant) -> dict[str, type[Condition]]:
"""Return the climate conditions."""
return CONDITIONS

View File

@@ -0,0 +1,20 @@
.condition_common: &condition_common
target:
entity:
domain: climate
fields:
behavior:
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
is_off: *condition_common
is_on: *condition_common
is_cooling: *condition_common
is_drying: *condition_common
is_heating: *condition_common

View File

@@ -1,4 +1,21 @@
{
"conditions": {
"is_cooling": {
"condition": "mdi:snowflake"
},
"is_drying": {
"condition": "mdi:water-percent"
},
"is_heating": {
"condition": "mdi:fire"
},
"is_off": {
"condition": "mdi:power-off"
},
"is_on": {
"condition": "mdi:power-on"
}
},
"entity_component": {
"_": {
"default": "mdi:thermostat",

View File

@@ -1,8 +1,62 @@
{
"common": {
"condition_behavior_description": "How the state should match on the targeted climate-control devices.",
"condition_behavior_name": "Behavior",
"trigger_behavior_description": "The behavior of the targeted climates to trigger on.",
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_cooling": {
"description": "Tests if one or more climate-control devices are cooling.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
}
},
"name": "If a climate-control device is cooling"
},
"is_drying": {
"description": "Tests if one or more climate-control devices are drying.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
}
},
"name": "If a climate-control device is drying"
},
"is_heating": {
"description": "Tests if one or more climate-control devices are heating.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
}
},
"name": "If a climate-control device is heating"
},
"is_off": {
"description": "Tests if one or more climate-control devices are off.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
}
},
"name": "If a climate-control device is off"
},
"is_on": {
"description": "Tests if one or more climate-control devices are on.",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::condition_behavior_description%]",
"name": "[%key:component::climate::common::condition_behavior_name%]"
}
},
"name": "If a climate-control device is on"
}
},
"device_automation": {
"action_type": {
"set_hvac_mode": "Change HVAC mode on {entity_name}",
@@ -181,6 +235,12 @@
}
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"hvac_mode": {
"options": {
"auto": "[%key:common::state::auto%]",

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import collections
from dataclasses import dataclass
from typing import Any, Self
@@ -141,22 +140,6 @@ class _SwingModeWrapper(DeviceWrapper):
return commands
def _filter_hvac_mode_mappings(tuya_range: list[str]) -> dict[str, HVACMode | None]:
"""Filter TUYA_HVAC_TO_HA modes that are not in the range.
If multiple Tuya modes map to the same HA mode, set the mapping to None to avoid
ambiguity when converting back from HA to Tuya modes.
"""
modes_in_range = {
tuya_mode: TUYA_HVAC_TO_HA.get(tuya_mode) for tuya_mode in tuya_range
}
modes_occurrences = collections.Counter(modes_in_range.values())
for key, value in modes_in_range.items():
if value is not None and modes_occurrences[value] > 1:
modes_in_range[key] = None
return modes_in_range
class _HvacModeWrapper(DPCodeEnumWrapper):
"""Wrapper for managing climate HVACMode."""
@@ -165,9 +148,10 @@ class _HvacModeWrapper(DPCodeEnumWrapper):
def __init__(self, dpcode: str, type_information: EnumTypeInformation) -> None:
"""Init _HvacModeWrapper."""
super().__init__(dpcode, type_information)
self._mappings = _filter_hvac_mode_mappings(type_information.range)
self.options = [
ha_mode for ha_mode in self._mappings.values() if ha_mode is not None
TUYA_HVAC_TO_HA[tuya_mode]
for tuya_mode in type_information.range
if tuya_mode in TUYA_HVAC_TO_HA
]
def read_device_status(self, device: CustomerDevice) -> HVACMode | None:
@@ -182,7 +166,7 @@ class _HvacModeWrapper(DPCodeEnumWrapper):
"""Convert value to raw value."""
return next(
tuya_mode
for tuya_mode, ha_mode in self._mappings.items()
for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items()
if ha_mode == value
)
@@ -195,9 +179,10 @@ class _PresetWrapper(DPCodeEnumWrapper):
def __init__(self, dpcode: str, type_information: EnumTypeInformation) -> None:
"""Init _PresetWrapper."""
super().__init__(dpcode, type_information)
mappings = _filter_hvac_mode_mappings(type_information.range)
self.options = [
tuya_mode for tuya_mode, ha_mode in mappings.items() if ha_mode is None
tuya_mode
for tuya_mode in type_information.range
if tuya_mode not in TUYA_HVAC_TO_HA
]
def read_device_status(self, device: CustomerDevice) -> str | None:

View File

@@ -419,6 +419,74 @@ def make_entity_state_condition(
return CustomCondition
class EntityStateAttributeConditionBase(EntityStateConditionBase):
"""State attribute condition."""
_attribute: str
_attribute_states: set[str]
@override
async def async_get_checker(self) -> ConditionChecker:
"""Get the condition checker."""
def check_any_match_attribute(attribute_values: list[str | None]) -> bool:
"""Test if any entity attribute matches the state."""
return any(
attr_value in self._attribute_states for attr_value in attribute_values
)
def check_all_match_attribute(attribute_values: list[str | None]) -> bool:
"""Test if all entity attributes match the state."""
return all(
attr_value in self._attribute_states for attr_value in attribute_values
)
matcher: Callable[[list[str | None]], bool]
if self._behavior == BEHAVIOR_ANY:
matcher = check_any_match_attribute
elif self._behavior == BEHAVIOR_ALL:
matcher = check_all_match_attribute
def test_attribute(**kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Test attribute condition."""
targeted_entities = async_extract_referenced_entity_ids(
self._hass, self._target_selection, expand_group=False
)
referenced_entity_ids = targeted_entities.referenced.union(
targeted_entities.indirectly_referenced
)
filtered_entity_ids = self.entity_filter(referenced_entity_ids)
attribute_values = [
_state.attributes.get(self._attribute)
for entity_id in filtered_entity_ids
if (_state := self._hass.states.get(entity_id))
and _state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
]
return matcher(attribute_values)
return test_attribute
def make_entity_state_attribute_condition(
domain: str, attribute: str, attribute_states: str | set[str]
) -> type[EntityStateAttributeConditionBase]:
"""Create a condition for entity attribute matching specific state(s)."""
if isinstance(attribute_states, str):
attribute_states_set = {attribute_states}
else:
attribute_states_set = attribute_states
class CustomCondition(EntityStateAttributeConditionBase):
"""Condition for entity attribute."""
_domain = domain
_attribute = attribute
_attribute_states = attribute_states_set
return CustomCondition
class ConditionProtocol(Protocol):
"""Define the format of condition modules."""

View File

@@ -0,0 +1,323 @@
"""Test climate conditions."""
from typing import Any
import pytest
from homeassistant.components.climate.const import (
ATTR_HVAC_ACTION,
HVACAction,
HVACMode,
)
from homeassistant.core import HomeAssistant
from tests.components import (
ConditionStateDescription,
assert_condition_gated_by_labs_flag,
create_target_condition,
other_states,
parametrize_condition_states,
parametrize_target_entities,
set_or_remove_state,
target_entities,
)
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture
async def target_climates(hass: HomeAssistant) -> list[str]:
"""Create multiple climate entities associated with different targets."""
return (await target_entities(hass, "climate"))["included"]
@pytest.mark.parametrize(
"condition",
[
"climate.is_off",
"climate.is_on",
"climate.is_cooling",
"climate.is_drying",
"climate.is_heating",
],
)
async def test_climate_conditions_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
) -> None:
"""Test the climate conditions are gated by the labs flag."""
await assert_condition_gated_by_labs_flag(hass, caplog, condition)
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="climate.is_off",
target_states=[HVACMode.OFF],
other_states=other_states(HVACMode.OFF),
),
*parametrize_condition_states(
condition="climate.is_on",
target_states=[
HVACMode.AUTO,
HVACMode.COOL,
HVACMode.DRY,
HVACMode.FAN_ONLY,
HVACMode.HEAT,
HVACMode.HEAT_COOL,
],
other_states=[HVACMode.OFF],
),
],
)
async def test_climate_state_condition_behavior_any(
hass: HomeAssistant,
target_climates: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the climate state condition with the 'any' behavior."""
other_entity_ids = set(target_climates) - {entity_id}
# Set all climates, including the tested climate, to the initial state
for eid in target_climates:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="any",
)
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
# Check if changing other climates also passes the condition
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="climate.is_off",
target_states=[HVACMode.OFF],
other_states=other_states(HVACMode.OFF),
),
*parametrize_condition_states(
condition="climate.is_on",
target_states=[
HVACMode.AUTO,
HVACMode.COOL,
HVACMode.DRY,
HVACMode.FAN_ONLY,
HVACMode.HEAT,
HVACMode.HEAT_COOL,
],
other_states=[HVACMode.OFF],
),
],
)
async def test_climate_state_condition_behavior_all(
hass: HomeAssistant,
target_climates: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the climate state condition with the 'all' behavior."""
other_entity_ids = set(target_climates) - {entity_id}
# Set all climates, including the tested climate, to the initial state
for eid in target_climates:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="all",
)
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"])
or (state["condition_true"] and entities_in_target == 1)
)
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"]) or state["condition_true"]
)
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="climate.is_cooling",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.COOLING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
*parametrize_condition_states(
condition="climate.is_drying",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.DRYING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
*parametrize_condition_states(
condition="climate.is_heating",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.HEATING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
],
)
async def test_climate_attribute_condition_behavior_any(
hass: HomeAssistant,
target_climates: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the climate attribute condition with the 'any' behavior."""
other_entity_ids = set(target_climates) - {entity_id}
# Set all climates, including the tested climate, to the initial state
for eid in target_climates:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="any",
)
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
# Check if changing other climates also passes the condition
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="climate.is_cooling",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.COOLING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
*parametrize_condition_states(
condition="climate.is_drying",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.DRYING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
*parametrize_condition_states(
condition="climate.is_heating",
target_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.HEATING})],
other_states=[(HVACMode.AUTO, {ATTR_HVAC_ACTION: HVACAction.IDLE})],
),
],
)
async def test_climate_attribute_condition_behavior_all(
hass: HomeAssistant,
target_climates: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the climate attribute condition with the 'all' behavior."""
other_entity_ids = set(target_climates) - {entity_id}
# Set all climates, including the tested climate, to the initial state
for eid in target_climates:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="all",
)
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"])
or (state["condition_true"] and entities_in_target == 1)
)
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"]) or state["condition_true"]
)

View File

@@ -5,7 +5,7 @@ from typing import Any
import pytest
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, ServiceCall
from tests.components import (
ConditionStateDescription,
@@ -137,6 +137,7 @@ async def test_light_state_condition_behavior_any(
)
async def test_light_state_condition_behavior_all(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_lights: list[str],
condition_target_config: dict,
entity_id: str,

View File

@@ -83,13 +83,13 @@
'capabilities': dict({
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
@@ -130,13 +130,13 @@
'friendly_name': 'Anbau',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 17>,
@@ -159,13 +159,13 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 70.0,
'min_temp': 1.0,
'preset_modes': list([
'holiday',
'auto',
'manual',
'eco',
]),
'target_temp_step': 0.5,
@@ -208,14 +208,14 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 70.0,
'min_temp': 1.0,
'preset_mode': None,
'preset_modes': list([
'holiday',
'auto',
'manual',
'eco',
]),
'supported_features': <ClimateEntityFeature: 17>,
@@ -453,13 +453,13 @@
'capabilities': dict({
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
@@ -501,14 +501,14 @@
'friendly_name': 'Empore',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': None,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 17>,
@@ -532,12 +532,12 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
@@ -580,13 +580,13 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 35.0,
'min_temp': 5.0,
'preset_mode': None,
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 401>,
@@ -1107,12 +1107,12 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 5.9,
'min_temp': 0.1,
'preset_modes': list([
'auto',
'manual',
'holiday',
]),
'target_temp_step': 0.5,
@@ -1155,13 +1155,13 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 5.9,
'min_temp': 0.1,
'preset_mode': None,
'preset_modes': list([
'auto',
'manual',
'holiday',
]),
'supported_features': <ClimateEntityFeature: 17>,
@@ -1185,13 +1185,10 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 90.0,
'min_temp': 5.0,
'preset_modes': list([
'auto',
'manual',
]),
'target_temp_step': 1.0,
}),
'config_entry_id': <ANY>,
@@ -1218,7 +1215,7 @@
'platform': 'tuya',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 401>,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'unique_id': 'tuya.sb3zdertrw50bgogkw',
'unit_of_measurement': None,
@@ -1232,15 +1229,11 @@
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 90.0,
'min_temp': 5.0,
'preset_mode': None,
'preset_modes': list([
'auto',
'manual',
]),
'supported_features': <ClimateEntityFeature: 401>,
'supported_features': <ClimateEntityFeature: 385>,
'target_temp_step': 1.0,
'temperature': 12.0,
}),