Initial public release

This commit is contained in:
ajp_anton
2026-05-30 15:44:40 +00:00
commit cc4e0c2c0f
56 changed files with 11840 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
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="Example Living")
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="Example Living")
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="Example Living")
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,)),
]