Compare commits

...

3 Commits

Author SHA1 Message Date
Erik
565aebd305 Update validator 2026-01-19 09:57:56 +01:00
Erik
8a9ca9bd98 Use hass.data[TRIGGERS] when instantiating triggers 2026-01-19 09:47:37 +01:00
Erik
cb5daaf3fe Proof of concept for implementing triggers in a different domain 2026-01-16 14:02:02 +01:00
7 changed files with 54 additions and 5 deletions

View File

@@ -176,6 +176,9 @@
}
},
"triggers": {
"_door.opened": {
"trigger": "mdi:door-open"
},
"occupancy_cleared": {
"trigger": "mdi:home-outline"
},

View File

@@ -332,6 +332,16 @@
},
"title": "Binary sensor",
"triggers": {
"_door.opened": {
"description": "Triggers after one or more occupancy doors open.",
"fields": {
"behavior": {
"description": "[%key:component::binary_sensor::common::trigger_behavior_description_occupancy%]",
"name": "[%key:component::binary_sensor::common::trigger_behavior_name%]"
}
},
"name": "Door opened"
},
"occupancy_cleared": {
"description": "Triggers after one or more occupancy sensors stop detecting occupancy.",
"fields": {

View File

@@ -53,6 +53,7 @@ def make_binary_sensor_trigger(
TRIGGERS: dict[str, type[Trigger]] = {
"_door.opened": make_binary_sensor_trigger(BinarySensorDeviceClass.DOOR, STATE_ON),
"occupancy_detected": make_binary_sensor_trigger(
BinarySensorDeviceClass.OCCUPANCY, STATE_ON
),

View File

@@ -23,3 +23,10 @@ occupancy_detected:
entity:
domain: binary_sensor
device_class: occupancy
_door.opened:
fields: *trigger_common_fields
target:
entity:
domain: binary_sensor
device_class: door

View File

@@ -655,10 +655,15 @@ def slug(value: Any) -> str:
raise vol.Invalid(f"invalid slug {value} (try {slg})")
def slugs(value: Any) -> list[str]:
"""Validate slugs separated by period."""
return [slug(item) for item in value.split(".")]
def underscore_slug(value: Any) -> str:
"""Validate value is a valid slug, possibly starting with an underscore."""
if value.startswith("_"):
return f"_{slug(value[1:])}"
return f"_{'.'.join(slugs(value[1:]))}"
return slug(value)

View File

@@ -1073,9 +1073,10 @@ async def _async_get_trigger_platform(
) -> tuple[str, TriggerProtocol]:
from homeassistant.components import automation # noqa: PLC0415
platform_and_sub_type = trigger_key.split(".")
platform = platform_and_sub_type[0]
platform = _PLATFORM_ALIASES.get(platform, platform)
if not (platform := hass.data[TRIGGERS].get(trigger_key)):
platform = _PLATFORM_ALIASES.get(trigger_key)
if not platform:
raise vol.Invalid(f"Invalid trigger '{trigger_key}' specified")
if automation.is_disabled_experimental_trigger(hass, platform):
raise vol.Invalid(
@@ -1433,6 +1434,10 @@ async def async_get_all_descriptions(
if (target := yaml_description.get("target")) is not None:
description["target"] = target
prefix, sep, _ = missing_trigger.partition(".")
if sep and domain != prefix:
description["translation_domain"] = domain
new_descriptions_cache[missing_trigger] = description
hass.data[TRIGGER_DESCRIPTION_CACHE] = new_descriptions_cache
return new_descriptions_cache

View File

@@ -66,6 +66,16 @@ async def test_bad_trigger_platform(hass: HomeAssistant) -> None:
async def test_trigger_subtype(hass: HomeAssistant) -> None:
"""Test trigger subtypes."""
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
return {
"subtype": Mock(),
}
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
with patch(
"homeassistant.helpers.trigger.async_get_integration",
return_value=MagicMock(async_get_platform=AsyncMock()),
@@ -530,6 +540,7 @@ async def test_platform_multiple_triggers(
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
config_1 = [{"platform": "test"}]
config_2 = [{"platform": "test.trig_2", "options": {"x": 1}}]
@@ -576,7 +587,9 @@ async def test_platform_multiple_triggers(
}
action_helper.action_calls.clear()
with pytest.raises(KeyError):
with pytest.raises(
vol.Invalid, match="Invalid trigger 'test.unknown_trig' specified"
):
await async_initialize_triggers(
hass, config_3, action_method, "test", "", log_cb
)
@@ -617,6 +630,7 @@ async def test_platform_migrate_trigger(hass: HomeAssistant) -> None:
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
config_1 = [{"platform": "test", "option_1": "value_1", "option_2": 2}]
config_2 = [{"platform": "test", "option_1": "value_1"}]
@@ -646,6 +660,7 @@ async def test_platform_backwards_compatibility_for_new_style_configs(
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", MockTriggerPlatform())
await async_setup_component(hass, "test", {})
config_old_style = [{"platform": "test", "option_1": "value_1", "option_2": 2}]
result = await async_validate_trigger_config(hass, config_old_style)
@@ -1255,6 +1270,7 @@ async def test_numerical_state_attribute_changed_trigger_config_validation(
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
with expected_result:
await async_validate_trigger_config(
@@ -1283,6 +1299,7 @@ async def test_numerical_state_attribute_changed_error_handling(
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
hass.states.async_set("test.test_entity", "on", {"test_attribute": 20})
@@ -1565,6 +1582,7 @@ async def test_numerical_state_attribute_crossed_threshold_trigger_config_valida
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.trigger", Mock(async_get_triggers=async_get_triggers))
await async_setup_component(hass, "test", {})
with expected_result:
await async_validate_trigger_config(