187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from ha_deconz_bridge.automation import (
|
|
ActionSpec,
|
|
AutomationEngine,
|
|
AutomationRule,
|
|
RemoteGestureDetector,
|
|
RemoteSpec,
|
|
TimeCondition,
|
|
automation_rules,
|
|
)
|
|
|
|
|
|
def test_hue_dimmer_short_press_maps_to_single_and_double() -> None:
|
|
remote = RemoteSpec("4", "room", "Room remote", double_press_window=0.5)
|
|
detector = RemoteGestureDetector(default_double_press_window=0.45)
|
|
|
|
first = detector.handle(
|
|
remote,
|
|
{"buttonevent": 1002, "eventduration": 1},
|
|
received_at=10.0,
|
|
)
|
|
detector.handle(remote, {"buttonevent": 1000, "eventduration": 0}, received_at=10.2)
|
|
second = detector.handle(
|
|
remote,
|
|
{"buttonevent": 1002, "eventduration": 1},
|
|
received_at=10.3,
|
|
)
|
|
|
|
assert [(event.button, event.gesture) for event in first] == [("top", "single")]
|
|
assert [(event.button, event.gesture) for event in second] == [("top", "double")]
|
|
|
|
|
|
def test_double_press_state_is_per_physical_remote() -> None:
|
|
detector = RemoteGestureDetector(default_double_press_window=0.45)
|
|
first_remote = RemoteSpec("4", "room", "First")
|
|
second_remote = RemoteSpec("5", "room", "Second")
|
|
|
|
detector.handle(first_remote, {"buttonevent": 1002}, received_at=10.0)
|
|
events = detector.handle(second_remote, {"buttonevent": 1002}, received_at=10.2)
|
|
|
|
assert [event.gesture for event in events] == ["single"]
|
|
|
|
|
|
def test_hue_dimmer_long_hold_maps_to_repeat_and_release() -> None:
|
|
remote = RemoteSpec("4", "room", "Room remote")
|
|
detector = RemoteGestureDetector(default_double_press_window=0.45)
|
|
|
|
repeat = detector.handle(
|
|
remote,
|
|
{"buttonevent": 2001, "eventduration": 8},
|
|
received_at=10.8,
|
|
)
|
|
release = detector.handle(
|
|
remote,
|
|
{"buttonevent": 2003, "eventduration": 22},
|
|
received_at=12.2,
|
|
)
|
|
|
|
assert [(event.button, event.gesture, event.eventduration, event.hold_count) for event in repeat] == [
|
|
("up", "hold", 8, 1)
|
|
]
|
|
assert [(event.button, event.gesture, event.eventduration) for event in release] == [
|
|
("up", "long", 22)
|
|
]
|
|
|
|
|
|
def test_double_hold_and_double_long_use_same_physical_remote() -> None:
|
|
remote = RemoteSpec("4", "room", "Room remote", double_press_window=0.5)
|
|
detector = RemoteGestureDetector(default_double_press_window=0.45)
|
|
|
|
detector.handle(remote, {"buttonevent": 1002}, received_at=10.0)
|
|
detector.handle(remote, {"buttonevent": 1000}, received_at=10.2)
|
|
first_hold = detector.handle(remote, {"buttonevent": 1001, "eventduration": 8}, received_at=10.4)
|
|
repeat_hold = detector.handle(remote, {"buttonevent": 1001, "eventduration": 16}, received_at=11.2)
|
|
release = detector.handle(remote, {"buttonevent": 1003, "eventduration": 20}, received_at=11.6)
|
|
|
|
assert [(event.gesture, event.hold_count) for event in first_hold] == [("double_hold", 1)]
|
|
assert [(event.gesture, event.hold_count) for event in repeat_hold] == [("double_hold", 2)]
|
|
assert [event.gesture for event in release] == ["double_long"]
|
|
|
|
|
|
def test_double_long_state_is_per_physical_remote() -> None:
|
|
detector = RemoteGestureDetector(default_double_press_window=0.45)
|
|
first_remote = RemoteSpec("4", "room", "First")
|
|
second_remote = RemoteSpec("5", "room", "Second")
|
|
|
|
detector.handle(first_remote, {"buttonevent": 1002}, received_at=10.0)
|
|
detector.handle(second_remote, {"buttonevent": 1000}, received_at=10.1)
|
|
events = detector.handle(second_remote, {"buttonevent": 1001}, received_at=10.2)
|
|
|
|
assert [event.gesture for event in events] == ["hold"]
|
|
|
|
|
|
def test_hold_count_gesture_patterns() -> None:
|
|
action = ActionSpec("turn_off", target="Living Room")
|
|
engine = AutomationEngine(
|
|
remotes={"4": RemoteSpec("4", "room", "Room remote")},
|
|
rules=(
|
|
AutomationRule("room", "up", "hold", (action,)),
|
|
AutomationRule("room", "up", "hold_2", (action,)),
|
|
AutomationRule("room", "up", "hold_2_2", (action,)),
|
|
AutomationRule("room", "up", "hold_2_3", (action,)),
|
|
AutomationRule("room", "up", "hold_3_2", (action,)),
|
|
),
|
|
default_double_press_window=0.45,
|
|
)
|
|
|
|
first = engine.handle_sensor_event("4", {"buttonevent": 2001}, received_at=10.0)
|
|
second = engine.handle_sensor_event("4", {"buttonevent": 2001}, received_at=11.0)
|
|
third = engine.handle_sensor_event("4", {"buttonevent": 2001}, received_at=12.0)
|
|
fourth = engine.handle_sensor_event("4", {"buttonevent": 2001}, received_at=13.0)
|
|
|
|
assert first is not None
|
|
assert second is not None
|
|
assert third is not None
|
|
assert fourth is not None
|
|
assert len(first[1]) == 1
|
|
assert len(second[1]) == 4
|
|
assert len(third[1]) == 3
|
|
assert len(fourth[1]) == 2
|
|
|
|
|
|
def test_automation_rules_bind_to_internal_remote_id() -> None:
|
|
action = ActionSpec("turn_off", target="Living Room")
|
|
engine = AutomationEngine(
|
|
remotes={
|
|
"4": RemoteSpec("4", "room", "First"),
|
|
"5": RemoteSpec("5", "room", "Second"),
|
|
},
|
|
rules=(
|
|
AutomationRule(
|
|
remote_id="room",
|
|
button="bottom",
|
|
gesture="single",
|
|
actions=(action,),
|
|
),
|
|
),
|
|
default_double_press_window=0.45,
|
|
)
|
|
|
|
first = engine.handle_sensor_event(
|
|
"4",
|
|
{"buttonevent": 4002},
|
|
received_at=10.0,
|
|
)
|
|
second = engine.handle_sensor_event(
|
|
"5",
|
|
{"buttonevent": 4002},
|
|
received_at=20.0,
|
|
)
|
|
|
|
assert first is not None
|
|
assert second is not None
|
|
assert first[1] == (action,)
|
|
assert second[1] == (action,)
|
|
|
|
|
|
def test_time_condition_handles_midnight_wrap() -> None:
|
|
condition = TimeCondition("22:00", "06:00")
|
|
|
|
assert condition.matches(datetime(2026, 1, 1, 23, 0))
|
|
assert condition.matches(datetime(2026, 1, 1, 5, 59))
|
|
assert not condition.matches(datetime(2026, 1, 1, 12, 0))
|
|
|
|
|
|
def test_automation_rules_can_be_built_from_nested_config() -> None:
|
|
action = ActionSpec("turn_off", target="Living Room")
|
|
|
|
rules = automation_rules(
|
|
{
|
|
"remote": {
|
|
"bottom": {
|
|
"single": (action,),
|
|
"hold_2_0": (action,),
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
assert [(rule.remote_id, rule.button, rule.gesture, rule.actions) for rule in rules] == [
|
|
("remote", "bottom", "single", (action,)),
|
|
("remote", "bottom", "hold_2_0", (action,)),
|
|
]
|