mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 17:49:37 +01:00
Exclude incompatible entities from humidity automations (#169898)
This commit is contained in:
@@ -14,9 +14,9 @@ from homeassistant.components.weather import (
|
||||
DOMAIN as WEATHER_DOMAIN,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.condition import Condition, make_entity_numerical_condition
|
||||
from homeassistant.helpers.condition import Condition, EntityNumericalConditionBase
|
||||
|
||||
HUMIDITY_DOMAIN_SPECS = {
|
||||
CLIMATE_DOMAIN: DomainSpec(
|
||||
@@ -31,8 +31,31 @@ HUMIDITY_DOMAIN_SPECS = {
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class HumidityCondition(EntityNumericalConditionBase):
|
||||
"""Condition for humidity value across multiple domains."""
|
||||
|
||||
_domain_specs = HUMIDITY_DOMAIN_SPECS
|
||||
_valid_unit = PERCENTAGE
|
||||
|
||||
def _should_include(self, state: State) -> bool:
|
||||
"""Skip attribute-source entities that lack the humidity attribute.
|
||||
|
||||
Mirrors the humidity trigger: for climate / humidifier / weather
|
||||
(attribute-based), the entity is filtered when the source attribute
|
||||
is absent; sensor entities (state-value-based) fall through to the
|
||||
base impl.
|
||||
"""
|
||||
if not super()._should_include(state):
|
||||
return False
|
||||
domain_spec = self._domain_specs[state.domain]
|
||||
if domain_spec.value_source is None:
|
||||
return True
|
||||
return state.attributes.get(domain_spec.value_source) is not None
|
||||
|
||||
|
||||
CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_value": make_entity_numerical_condition(HUMIDITY_DOMAIN_SPECS, PERCENTAGE),
|
||||
"is_value": HumidityCondition,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@ from homeassistant.components.weather import (
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
DOMAIN as WEATHER_DOMAIN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.automation import DomainSpec
|
||||
from homeassistant.helpers.trigger import (
|
||||
EntityNumericalStateChangedTriggerBase,
|
||||
EntityNumericalStateCrossedThresholdTriggerBase,
|
||||
EntityNumericalStateTriggerBase,
|
||||
Trigger,
|
||||
make_entity_numerical_state_changed_trigger,
|
||||
make_entity_numerical_state_crossed_threshold_trigger,
|
||||
)
|
||||
|
||||
HUMIDITY_DOMAIN_SPECS: dict[str, DomainSpec] = {
|
||||
@@ -36,13 +37,46 @@ HUMIDITY_DOMAIN_SPECS: dict[str, DomainSpec] = {
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class _HumidityTriggerMixin(EntityNumericalStateTriggerBase):
|
||||
"""Mixin for humidity triggers providing entity filtering."""
|
||||
|
||||
_domain_specs = HUMIDITY_DOMAIN_SPECS
|
||||
_valid_unit = "%"
|
||||
|
||||
def _should_include(self, state: State) -> bool:
|
||||
"""Skip attribute-source entities that lack the humidity attribute.
|
||||
|
||||
For domains whose tracked value comes from an attribute
|
||||
(climate / humidifier / weather), require the attribute to be
|
||||
present; otherwise the all/count check would treat an entity that
|
||||
cannot report a humidity as a non-match and block behavior=last.
|
||||
Sensor entities source their value from `state.state`, so they
|
||||
fall through to the base impl.
|
||||
"""
|
||||
if not super()._should_include(state):
|
||||
return False
|
||||
domain_spec = self._domain_specs[state.domain]
|
||||
if domain_spec.value_source is None:
|
||||
return True
|
||||
return state.attributes.get(domain_spec.value_source) is not None
|
||||
|
||||
|
||||
class HumidityChangedTrigger(
|
||||
_HumidityTriggerMixin, EntityNumericalStateChangedTriggerBase
|
||||
):
|
||||
"""Trigger for humidity value changes across multiple domains."""
|
||||
|
||||
|
||||
class HumidityCrossedThresholdTrigger(
|
||||
_HumidityTriggerMixin, EntityNumericalStateCrossedThresholdTriggerBase
|
||||
):
|
||||
"""Trigger for humidity value crossing a threshold across multiple domains."""
|
||||
|
||||
|
||||
TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"changed": make_entity_numerical_state_changed_trigger(
|
||||
HUMIDITY_DOMAIN_SPECS, valid_unit="%"
|
||||
),
|
||||
"crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
|
||||
HUMIDITY_DOMAIN_SPECS, valid_unit="%"
|
||||
),
|
||||
"changed": HumidityChangedTrigger,
|
||||
"crossed_threshold": HumidityCrossedThresholdTrigger,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ async def test_humidity_sensor_condition_behavior_all(
|
||||
"humidity.is_value",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_climate_condition_behavior_any(
|
||||
@@ -215,6 +216,7 @@ async def test_humidity_climate_condition_behavior_any(
|
||||
"humidity.is_value",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_climate_condition_behavior_all(
|
||||
@@ -251,6 +253,7 @@ async def test_humidity_climate_condition_behavior_all(
|
||||
"humidity.is_value",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_humidifier_condition_behavior_any(
|
||||
@@ -287,6 +290,7 @@ async def test_humidity_humidifier_condition_behavior_any(
|
||||
"humidity.is_value",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_humidifier_condition_behavior_all(
|
||||
@@ -323,6 +327,7 @@ async def test_humidity_humidifier_condition_behavior_all(
|
||||
"humidity.is_value",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_weather_condition_behavior_any(
|
||||
@@ -359,6 +364,7 @@ async def test_humidity_weather_condition_behavior_any(
|
||||
"humidity.is_value",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
)
|
||||
async def test_humidity_weather_condition_behavior_all(
|
||||
|
||||
@@ -240,12 +240,16 @@ async def test_humidity_trigger_sensor_crossed_threshold_behavior_last(
|
||||
("trigger", "trigger_options", "states"),
|
||||
[
|
||||
*parametrize_numerical_attribute_changed_trigger_states(
|
||||
"humidity.changed", HVACMode.AUTO, CLIMATE_ATTR_CURRENT_HUMIDITY
|
||||
"humidity.changed",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
*parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
||||
"humidity.crossed_threshold",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -284,6 +288,7 @@ async def test_humidity_trigger_climate_behavior_any(
|
||||
"humidity.crossed_threshold",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -322,6 +327,7 @@ async def test_humidity_trigger_climate_crossed_threshold_behavior_first(
|
||||
"humidity.crossed_threshold",
|
||||
HVACMode.AUTO,
|
||||
CLIMATE_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -360,12 +366,16 @@ async def test_humidity_trigger_climate_crossed_threshold_behavior_last(
|
||||
("trigger", "trigger_options", "states"),
|
||||
[
|
||||
*parametrize_numerical_attribute_changed_trigger_states(
|
||||
"humidity.changed", STATE_ON, HUMIDIFIER_ATTR_CURRENT_HUMIDITY
|
||||
"humidity.changed",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
*parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
||||
"humidity.crossed_threshold",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -404,6 +414,7 @@ async def test_humidity_trigger_humidifier_behavior_any(
|
||||
"humidity.crossed_threshold",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -442,6 +453,7 @@ async def test_humidity_trigger_humidifier_crossed_threshold_behavior_first(
|
||||
"humidity.crossed_threshold",
|
||||
STATE_ON,
|
||||
HUMIDIFIER_ATTR_CURRENT_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -480,12 +492,16 @@ async def test_humidity_trigger_humidifier_crossed_threshold_behavior_last(
|
||||
("trigger", "trigger_options", "states"),
|
||||
[
|
||||
*parametrize_numerical_attribute_changed_trigger_states(
|
||||
"humidity.changed", "sunny", ATTR_WEATHER_HUMIDITY
|
||||
"humidity.changed",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
*parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
||||
"humidity.crossed_threshold",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -524,6 +540,7 @@ async def test_humidity_trigger_weather_behavior_any(
|
||||
"humidity.crossed_threshold",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -562,6 +579,7 @@ async def test_humidity_trigger_weather_crossed_threshold_behavior_first(
|
||||
"humidity.crossed_threshold",
|
||||
"sunny",
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
attribute_required=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user