Compare commits

...

2 Commits

Author SHA1 Message Date
Erik Montnemery
261322b9de Merge branch 'dev' into add_cover_triggers_xxx_closed 2025-11-27 11:09:34 +01:00
Erik
b467186319 Add cover entity triggers xxx_closed 2025-11-27 08:15:06 +01:00
2 changed files with 122 additions and 0 deletions

View File

@@ -19,8 +19,17 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from . import ATTR_CURRENT_POSITION, CoverDeviceClass, CoverState
from .const import DOMAIN
ATTR_FULLY_CLOSED: Final = "fully_closed"
ATTR_FULLY_OPENED: Final = "fully_opened"
COVER_CLOSED_TRIGGER_SCHEMA = ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST.extend(
{
vol.Required(CONF_OPTIONS): {
vol.Required(ATTR_FULLY_CLOSED, default=False): bool,
},
}
)
COVER_OPENED_TRIGGER_SCHEMA = ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST.extend(
{
vol.Required(CONF_OPTIONS): {
@@ -72,6 +81,19 @@ class CoverOpenedClosedTrigger(EntityTriggerBase):
}
class CoverClosedTrigger(CoverOpenedClosedTrigger):
"""Class for cover closed triggers."""
_schema = COVER_CLOSED_TRIGGER_SCHEMA
_to_states = {CoverState.CLOSED, CoverState.CLOSING}
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize the state trigger."""
super().__init__(hass, config)
if self._options.get(ATTR_FULLY_CLOSED):
self._attribute_value = 0
class CoverOpenedTrigger(CoverOpenedClosedTrigger):
"""Class for cover opened triggers."""
@@ -85,6 +107,19 @@ class CoverOpenedTrigger(CoverOpenedClosedTrigger):
self._attribute_value = 100
def make_cover_closed_trigger(
device_class: CoverDeviceClass | None,
) -> type[CoverClosedTrigger]:
"""Create an entity state attribute trigger class."""
class CustomTrigger(CoverClosedTrigger):
"""Trigger for entity state changes."""
_device_class = device_class
return CustomTrigger
def make_cover_opened_trigger(
device_class: CoverDeviceClass | None,
) -> type[CoverOpenedTrigger]:
@@ -99,6 +134,15 @@ def make_cover_opened_trigger(
TRIGGERS: dict[str, type[Trigger]] = {
"awning_closed": make_cover_closed_trigger(CoverDeviceClass.AWNING),
"blind_closed": make_cover_closed_trigger(CoverDeviceClass.BLIND),
"curtain_closed": make_cover_closed_trigger(CoverDeviceClass.CURTAIN),
"door_closed": make_cover_closed_trigger(CoverDeviceClass.DOOR),
"garage_closed": make_cover_closed_trigger(CoverDeviceClass.GARAGE),
"gate_closed": make_cover_closed_trigger(CoverDeviceClass.GATE),
"shade_closed": make_cover_closed_trigger(CoverDeviceClass.SHADE),
"shutter_closed": make_cover_closed_trigger(CoverDeviceClass.SHUTTER),
"window_closed": make_cover_closed_trigger(CoverDeviceClass.WINDOW),
"awning_opened": make_cover_opened_trigger(CoverDeviceClass.AWNING),
"blind_opened": make_cover_opened_trigger(CoverDeviceClass.BLIND),
"curtain_opened": make_cover_opened_trigger(CoverDeviceClass.CURTAIN),

View File

@@ -41,6 +41,57 @@ async def target_covers(hass: HomeAssistant) -> list[str]:
return await target_entities(hass, "cover")
def parametrize_closed_trigger_states(
trigger: str, device_class: str
) -> list[tuple[str, dict, str, list[StateDescription]]]:
"""Parametrize states and expected service call counts.
Returns a list of tuples with (trigger, trigger_options,
list of StateDescription).
"""
additional_attributes = {ATTR_DEVICE_CLASS: device_class}
return [
# Test fully_closed = True
*(
(s[0], {"fully_closed": True}, *s[1:])
for s in parametrize_trigger_states(
trigger=trigger,
target_states=[
(CoverState.CLOSED, {}),
(CoverState.CLOSING, {}),
(CoverState.CLOSED, {ATTR_CURRENT_POSITION: 0}),
(CoverState.CLOSING, {ATTR_CURRENT_POSITION: 0}),
],
other_states=[
(CoverState.OPEN, {}),
(CoverState.CLOSED, {ATTR_CURRENT_POSITION: 1}),
],
additional_attributes=additional_attributes,
trigger_from_none=False,
)
),
# Test fully_closed = False
*(
(s[0], {}, *s[1:])
for s in parametrize_trigger_states(
trigger=trigger,
target_states=[
(CoverState.CLOSED, {}),
(CoverState.CLOSING, {}),
(CoverState.CLOSED, {ATTR_CURRENT_POSITION: 99}),
(CoverState.CLOSING, {ATTR_CURRENT_POSITION: 99}),
],
other_states=[
(CoverState.OPEN, {}),
(CoverState.OPEN, {ATTR_CURRENT_POSITION: 1}),
],
additional_attributes=additional_attributes,
trigger_from_none=False,
)
),
]
def parametrize_opened_trigger_states(
trigger: str, device_class: str
) -> list[tuple[str, dict, str, list[StateDescription]]]:
@@ -127,6 +178,15 @@ async def test_cover_triggers_gated_by_labs_flag(
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_closed_trigger_states("cover.awning_closed", "awning"),
*parametrize_closed_trigger_states("cover.blind_closed", "blind"),
*parametrize_closed_trigger_states("cover.curtain_closed", "curtain"),
*parametrize_closed_trigger_states("cover.door_closed", "door"),
*parametrize_closed_trigger_states("cover.garage_closed", "garage"),
*parametrize_closed_trigger_states("cover.gate_closed", "gate"),
*parametrize_closed_trigger_states("cover.shade_closed", "shade"),
*parametrize_closed_trigger_states("cover.shutter_closed", "shutter"),
*parametrize_closed_trigger_states("cover.window_closed", "window"),
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),
@@ -185,6 +245,15 @@ async def test_cover_state_attribute_trigger_behavior_any(
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_closed_trigger_states("cover.awning_closed", "awning"),
*parametrize_closed_trigger_states("cover.blind_closed", "blind"),
*parametrize_closed_trigger_states("cover.curtain_closed", "curtain"),
*parametrize_closed_trigger_states("cover.door_closed", "door"),
*parametrize_closed_trigger_states("cover.garage_closed", "garage"),
*parametrize_closed_trigger_states("cover.gate_closed", "gate"),
*parametrize_closed_trigger_states("cover.shade_closed", "shade"),
*parametrize_closed_trigger_states("cover.shutter_closed", "shutter"),
*parametrize_closed_trigger_states("cover.window_closed", "window"),
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),
@@ -247,6 +316,15 @@ async def test_cover_state_attribute_trigger_behavior_first(
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_closed_trigger_states("cover.awning_closed", "awning"),
*parametrize_closed_trigger_states("cover.blind_closed", "blind"),
*parametrize_closed_trigger_states("cover.curtain_closed", "curtain"),
*parametrize_closed_trigger_states("cover.door_closed", "door"),
*parametrize_closed_trigger_states("cover.garage_closed", "garage"),
*parametrize_closed_trigger_states("cover.gate_closed", "gate"),
*parametrize_closed_trigger_states("cover.shade_closed", "shade"),
*parametrize_closed_trigger_states("cover.shutter_closed", "shutter"),
*parametrize_closed_trigger_states("cover.window_closed", "window"),
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),