Compare commits

...

2 Commits

Author SHA1 Message Date
mib1185
6608db6098 fix test case naming 2026-02-24 23:10:48 +00:00
mib1185
bfce7a6893 add number.changed trigger 2026-02-24 23:00:13 +00:00
7 changed files with 175 additions and 1 deletions

View File

@@ -148,6 +148,7 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"light",
"lock",
"media_player",
"number",
"person",
"scene",
"siren",

View File

@@ -173,5 +173,10 @@
"set_value": {
"service": "mdi:numeric"
}
},
"triggers": {
"changed": {
"trigger": "mdi:counter"
}
}
}

View File

@@ -204,5 +204,11 @@
"name": "Set"
}
},
"title": "Number"
"title": "Number",
"triggers": {
"changed": {
"description": "Triggers when a number value changes.",
"name": "Number changed"
}
}
}

View File

@@ -0,0 +1,18 @@
"""Provides triggers for number entities."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers.trigger import (
Trigger,
make_entity_numerical_state_changed_trigger,
)
from .const import DOMAIN
TRIGGERS: dict[str, type[Trigger]] = {
"changed": make_entity_numerical_state_changed_trigger(DOMAIN),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for number entities."""
return TRIGGERS

View File

@@ -0,0 +1,4 @@
changed:
target:
entity:
domain: number

View File

@@ -657,6 +657,19 @@ class EntityNumericalStateAttributeChangedTriggerBase(EntityTriggerBase):
return True
class EntityNumericalStateChangedTriggerBase(EntityTriggerBase):
"""Trigger for numerical state changes."""
def is_valid_state(self, state: State) -> bool:
"""Check if the new state matches the expected one."""
try:
float(state.state)
except TypeError, ValueError:
# State is not a valid number, don't trigger
return False
return True
CONF_LOWER_LIMIT = "lower_limit"
CONF_UPPER_LIMIT = "upper_limit"
CONF_THRESHOLD_TYPE = "threshold_type"
@@ -855,6 +868,19 @@ def make_entity_numerical_state_attribute_crossed_threshold_trigger(
return CustomTrigger
def make_entity_numerical_state_changed_trigger(
domain: str,
) -> type[EntityNumericalStateChangedTriggerBase]:
"""Create a trigger for numerical state change."""
class CustomTrigger(EntityNumericalStateChangedTriggerBase):
"""Trigger for numerical state changes."""
_domain = domain
return CustomTrigger
def make_entity_target_state_attribute_trigger(
domain: str, attribute: str, to_state: str
) -> type[EntityTargetStateAttributeTriggerBase]:

View File

@@ -0,0 +1,114 @@
"""Test number entity trigger."""
import pytest
from homeassistant.components.number.const import DOMAIN
from homeassistant.const import ATTR_LABEL_ID, CONF_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall
from tests.components import (
TriggerStateDescription,
arm_trigger,
parametrize_target_entities,
set_or_remove_state,
target_entities,
)
@pytest.fixture
async def target_numbers(hass: HomeAssistant) -> list[str]:
"""Create multiple number entities associated with different targets."""
return (await target_entities(hass, DOMAIN))["included"]
@pytest.mark.parametrize(
"trigger_key",
[
"number.changed",
],
)
async def test_number_triggers_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
) -> None:
"""Test the number entity triggers are gated by the labs flag."""
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
assert (
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
f"'{trigger_key}' requires the experimental 'New triggers and conditions' "
"feature to be enabled in Home Assistant Labs settings (feature flag: "
"'new_triggers_conditions')"
) in caplog.text
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
)
@pytest.mark.parametrize(
("trigger", "states"),
[
(
"number.changed",
[
{"included": {"state": None, "attributes": {}}, "count": 0},
{"included": {"state": "1", "attributes": {}}, "count": 0},
{"included": {"state": "2", "attributes": {}}, "count": 1},
],
),
(
"number.changed",
[
{"included": {"state": "1", "attributes": {}}, "count": 0},
{"included": {"state": "1.1", "attributes": {}}, "count": 1},
{"included": {"state": "1", "attributes": {}}, "count": 1},
{"included": {"state": None, "attributes": {}}, "count": 0},
{"included": {"state": "2", "attributes": {}}, "count": 0},
{"included": {"state": "1.5", "attributes": {}}, "count": 1},
],
),
(
"number.changed",
[
{"included": {"state": "1", "attributes": {}}, "count": 0},
{"included": {"state": "not a number", "attributes": {}}, "count": 0},
{"included": {"state": "2", "attributes": {}}, "count": 1},
],
),
],
)
async def test_number_changed_trigger_behavior(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_numbers: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[TriggerStateDescription],
) -> None:
"""Test that the number changed trigger behaves correctly."""
other_entity_ids = set(target_numbers) - {entity_id}
# Set all numbers, including the tested number, to the initial state
for eid in target_numbers:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, {}, trigger_target_config)
for state in states[1:]:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Check that changing other numbers also triggers
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 len(service_calls) == (entities_in_target - 1) * state["count"]
service_calls.clear()