zencontrol-python 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/live/controller.py +48 -0
- examples/live/devices.py +48 -0
- examples/live/events.py +119 -0
- examples/live/gear.py +86 -0
- examples/live/groups.py +53 -0
- examples/live/instances.py +60 -0
- examples/live/interface.py +56 -0
- examples/live/interface_events.py +80 -0
- examples/live/leds.py +44 -0
- examples/live/level_change_v2.py +77 -0
- examples/live/multicast.py +52 -0
- examples/live/multicast_raw.py +78 -0
- examples/live/profiles.py +40 -0
- examples/live/tpi_event_listener.py +244 -0
- examples/live/variables.py +44 -0
- examples/mqtt_bridge.py +7 -37
- zencontrol/__init__.py +4 -2
- zencontrol/api/models.py +127 -98
- zencontrol/api/protocol.py +199 -150
- zencontrol/api/types.py +8 -6
- zencontrol/exceptions.py +13 -2
- zencontrol/interface/interface.py +177 -159
- zencontrol/io/command.py +82 -58
- zencontrol/io/event.py +13 -14
- zencontrol/utils.py +4 -2
- {zencontrol_python-0.1.1.dist-info → zencontrol_python-0.1.3.dist-info}/METADATA +29 -18
- zencontrol_python-0.1.3.dist-info/RECORD +36 -0
- zencontrol_python-0.1.1.dist-info/RECORD +0 -21
- {zencontrol_python-0.1.1.dist-info → zencontrol_python-0.1.3.dist-info}/WHEEL +0 -0
- {zencontrol_python-0.1.1.dist-info → zencontrol_python-0.1.3.dist-info}/entry_points.txt +0 -0
- {zencontrol_python-0.1.1.dist-info → zencontrol_python-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {zencontrol_python-0.1.1.dist-info → zencontrol_python-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test the async ZenProtocol with controller queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=True) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing ZenController queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
# Query controller version
|
|
21
|
+
version = await tpi.query_controller_version_number(ctrl)
|
|
22
|
+
print(f"ZenController version: {version}")
|
|
23
|
+
|
|
24
|
+
# Query controller label
|
|
25
|
+
controller_label = await tpi.query_controller_label(ctrl)
|
|
26
|
+
print(f"ZenController label: {controller_label}")
|
|
27
|
+
|
|
28
|
+
# Query controller fitting number
|
|
29
|
+
controller_fitting_number = await tpi.query_controller_fitting_number(ctrl)
|
|
30
|
+
print(f"ZenController fitting number: {controller_fitting_number}")
|
|
31
|
+
|
|
32
|
+
# Query startup status
|
|
33
|
+
startup_complete = await tpi.query_controller_startup_complete(ctrl)
|
|
34
|
+
print(f"ZenController startup complete: {startup_complete}")
|
|
35
|
+
|
|
36
|
+
# Query DALI bus status
|
|
37
|
+
dali_ready = await tpi.query_is_dali_ready(ctrl)
|
|
38
|
+
print(f"DALI bus is ready: {dali_ready}")
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
print(f"Error during testing: {e}")
|
|
42
|
+
|
|
43
|
+
print("=" * 50)
|
|
44
|
+
print("Test completed!")
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
run_with_keyboard_interrupt(main)
|
|
48
|
+
|
examples/live/devices.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test DALI device queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=False) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing DALI device queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
addresses = await tpi.query_dali_addresses_with_instances(ctrl, 0)
|
|
21
|
+
print(f"Addresses with instances:")
|
|
22
|
+
|
|
23
|
+
for address in addresses:
|
|
24
|
+
print(f" {address.number}")
|
|
25
|
+
|
|
26
|
+
label = await tpi.query_dali_device_label(address, generic_if_none=True)
|
|
27
|
+
print(f" label: {label}")
|
|
28
|
+
|
|
29
|
+
operating_mode = await tpi.query_operating_mode_by_address(address)
|
|
30
|
+
print(f" operating mode: {operating_mode}")
|
|
31
|
+
|
|
32
|
+
serial = await tpi.query_dali_serial(address)
|
|
33
|
+
print(f" serial: {serial}")
|
|
34
|
+
|
|
35
|
+
ean = await tpi.query_dali_ean(address)
|
|
36
|
+
print(f" ean: {ean}")
|
|
37
|
+
|
|
38
|
+
fitting = await tpi.query_dali_fitting_number(address)
|
|
39
|
+
print(f" fitting: {fitting}")
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Error during testing: {e}")
|
|
43
|
+
|
|
44
|
+
print("=" * 50)
|
|
45
|
+
print("Test completed!")
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
run_with_keyboard_interrupt(main)
|
examples/live/events.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import yaml
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
9
|
+
if str(ROOT) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(ROOT))
|
|
11
|
+
|
|
12
|
+
sys.stdout.reconfigure(line_buffering=True)
|
|
13
|
+
|
|
14
|
+
from zencontrol import ZenProtocol, ZenController, ZenAddress, ZenInstance, run_with_keyboard_interrupt
|
|
15
|
+
|
|
16
|
+
CONFIG_PATH = Path(__file__).resolve().parents[2] / "tests" / "config.yaml"
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
|
|
19
|
+
logger = logging.getLogger("test_events")
|
|
20
|
+
|
|
21
|
+
# Global timing variable for event timing
|
|
22
|
+
timevar = None
|
|
23
|
+
|
|
24
|
+
def ms():
|
|
25
|
+
"""Print time since last call in milliseconds"""
|
|
26
|
+
global timevar
|
|
27
|
+
timevar = timevar or time.time()
|
|
28
|
+
msecs = (time.time() - timevar) * 1000
|
|
29
|
+
timevar = time.time()
|
|
30
|
+
print(f"{msecs:.1f} ms")
|
|
31
|
+
|
|
32
|
+
# Event handlers — must be async; protocol awaits them
|
|
33
|
+
async def button_press_event(instance: ZenInstance, payload: bytes) -> None:
|
|
34
|
+
ms()
|
|
35
|
+
print(f"Button Press Event - ECD {instance.address.number} instance {instance.number}")
|
|
36
|
+
|
|
37
|
+
async def level_change_event(address: ZenAddress, arc_level: int, payload: bytes) -> None:
|
|
38
|
+
ms()
|
|
39
|
+
print(f"Level Change Event - {address.type} {address.number} arc_level {arc_level}")
|
|
40
|
+
|
|
41
|
+
async def group_level_change_event(address: ZenAddress, arc_level: int, payload: bytes) -> None:
|
|
42
|
+
ms()
|
|
43
|
+
print(f"Level Change Event Group - {address.type} {address.number} arc_level {arc_level}")
|
|
44
|
+
|
|
45
|
+
async def scene_change_event(address: ZenAddress, scene: int, active: bool, payload: bytes) -> None:
|
|
46
|
+
ms()
|
|
47
|
+
print(f"Scene Change Event - {address.type} {address.number} scene {scene}")
|
|
48
|
+
|
|
49
|
+
async def colour_change_event(address: ZenAddress, colour, payload: bytes) -> None:
|
|
50
|
+
ms()
|
|
51
|
+
print(f"Colour Change Event - {address.type} {address.number} colour {colour}")
|
|
52
|
+
|
|
53
|
+
def check_event_listener(tpi: ZenProtocol) -> None:
|
|
54
|
+
"""Print listener health — call periodically to detect silent task death."""
|
|
55
|
+
task = tpi.event_task
|
|
56
|
+
listener = tpi.event_listener
|
|
57
|
+
|
|
58
|
+
if task is None:
|
|
59
|
+
print("LISTENER: event_task not started")
|
|
60
|
+
return
|
|
61
|
+
if task.done():
|
|
62
|
+
exc = task.exception()
|
|
63
|
+
if exc:
|
|
64
|
+
print(f"LISTENER: event_task died with {type(exc).__name__}: {exc}")
|
|
65
|
+
else:
|
|
66
|
+
print("LISTENER: event_task finished unexpectedly")
|
|
67
|
+
elif listener is None or not listener.is_listening():
|
|
68
|
+
print("LISTENER: event_task running but socket is closed")
|
|
69
|
+
else:
|
|
70
|
+
print(f"LISTENER: ok (queue size {listener._event_queue.qsize()})")
|
|
71
|
+
|
|
72
|
+
async def main():
|
|
73
|
+
"""Test async ZenProtocol with event monitoring"""
|
|
74
|
+
config = yaml.safe_load(CONFIG_PATH.read_text())
|
|
75
|
+
|
|
76
|
+
async with ZenProtocol(print_traffic=True, unicast=False, logger=logger) as tpi:
|
|
77
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
78
|
+
tpi.set_controllers([ctrl])
|
|
79
|
+
|
|
80
|
+
print("Testing ZenProtocol Event Monitoring...")
|
|
81
|
+
print("=" * 60)
|
|
82
|
+
|
|
83
|
+
tpi.set_callbacks(
|
|
84
|
+
button_press_callback=button_press_event,
|
|
85
|
+
level_change_callback=level_change_event,
|
|
86
|
+
group_level_change_callback=group_level_change_event,
|
|
87
|
+
scene_change_callback=scene_change_event,
|
|
88
|
+
colour_change_callback=colour_change_event
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
print("Querying TPI event configuration...")
|
|
93
|
+
unicast_config = await tpi.query_tpi_event_unicast_address(ctrl)
|
|
94
|
+
print(f"TPI Event Unicast Address: {unicast_config}")
|
|
95
|
+
|
|
96
|
+
emit_state = await tpi.query_tpi_event_emit_state(ctrl)
|
|
97
|
+
print(f"TPI Event Emit State: {emit_state}")
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"Error querying event configuration: {e}")
|
|
101
|
+
|
|
102
|
+
print("\nStarting event monitoring...")
|
|
103
|
+
print("Press Ctrl+C to stop")
|
|
104
|
+
print("=" * 60)
|
|
105
|
+
|
|
106
|
+
await tpi.start_event_monitoring()
|
|
107
|
+
check_event_listener(tpi)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
while True:
|
|
111
|
+
await asyncio.sleep(5)
|
|
112
|
+
check_event_listener(tpi)
|
|
113
|
+
except KeyboardInterrupt:
|
|
114
|
+
print("\nStopping event monitoring...")
|
|
115
|
+
await tpi.stop_event_monitoring()
|
|
116
|
+
print("Event monitoring stopped.")
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
run_with_keyboard_interrupt(main)
|
examples/live/gear.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test control gear queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=False) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing control gear queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
addresses = await tpi.query_control_gear_dali_addresses(ctrl)
|
|
21
|
+
print(f"Control gears")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
for address in addresses[:5]: # Only first 5 gears
|
|
25
|
+
print(f" {address.number}")
|
|
26
|
+
|
|
27
|
+
level = await tpi.dali_query_level(address)
|
|
28
|
+
print(f" current level: {level}")
|
|
29
|
+
|
|
30
|
+
label = await tpi.query_dali_device_label(address)
|
|
31
|
+
print(f" label: {label}")
|
|
32
|
+
|
|
33
|
+
groups = await tpi.query_group_membership_by_address(address)
|
|
34
|
+
print(f" groups: {[group.number for group in groups]}")
|
|
35
|
+
|
|
36
|
+
type = await tpi.dali_query_cg_type(address)
|
|
37
|
+
print(f" type: {type}")
|
|
38
|
+
|
|
39
|
+
colour = await tpi.query_dali_colour(address)
|
|
40
|
+
print(f" colour: {colour}")
|
|
41
|
+
|
|
42
|
+
cgtype = await tpi.query_dali_colour_features(address)
|
|
43
|
+
print(f" colour features: {cgtype}")
|
|
44
|
+
|
|
45
|
+
colour_temp_limits = await tpi.query_dali_colour_temp_limits(address)
|
|
46
|
+
print(f" colour temp limits: {colour_temp_limits}")
|
|
47
|
+
|
|
48
|
+
fitting = await tpi.query_dali_fitting_number(address)
|
|
49
|
+
print(f" fitting: {fitting}")
|
|
50
|
+
|
|
51
|
+
ean = await tpi.query_dali_ean(address)
|
|
52
|
+
print(f" ean: {ean}")
|
|
53
|
+
|
|
54
|
+
serial = await tpi.query_dali_serial(address)
|
|
55
|
+
print(f" serial: {serial}")
|
|
56
|
+
|
|
57
|
+
last_scene = await tpi.dali_query_last_scene(address)
|
|
58
|
+
print(f" last scene: {last_scene}")
|
|
59
|
+
|
|
60
|
+
last_scene_is_current = await tpi.dali_query_last_scene_is_current(address)
|
|
61
|
+
print(f" last scene is current: {last_scene_is_current}")
|
|
62
|
+
|
|
63
|
+
status = await tpi.dali_query_control_gear_status(address)
|
|
64
|
+
print(f" status: {status}")
|
|
65
|
+
|
|
66
|
+
scenes = await tpi.query_scene_numbers_by_address(address)
|
|
67
|
+
print(f" scenes with levels: {scenes}")
|
|
68
|
+
|
|
69
|
+
scenes = await tpi.query_colour_scene_membership_by_address(address)
|
|
70
|
+
print(f" scenes with colours: {scenes}")
|
|
71
|
+
|
|
72
|
+
levels = await tpi.query_scene_levels_by_address(address)
|
|
73
|
+
print(f" scene levels: {levels}")
|
|
74
|
+
|
|
75
|
+
scene_data = await tpi.query_scene_colours_by_address(address)
|
|
76
|
+
print(f" scene colour data: {scene_data}")
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Error during testing: {e}")
|
|
80
|
+
|
|
81
|
+
print("=" * 50)
|
|
82
|
+
print("Test completed!")
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
run_with_keyboard_interrupt(main)
|
|
86
|
+
|
examples/live/groups.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test group queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=True) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing group queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
addresses = await tpi.query_group_numbers(ctrl)
|
|
21
|
+
print(f"Group")
|
|
22
|
+
|
|
23
|
+
for address in addresses:
|
|
24
|
+
print(f" {address.number}")
|
|
25
|
+
name = await tpi.query_group_label(address)
|
|
26
|
+
print(f" name: {name}")
|
|
27
|
+
|
|
28
|
+
information = await tpi.query_group_by_number(address)
|
|
29
|
+
print(f" information: {information}")
|
|
30
|
+
|
|
31
|
+
scenes = await tpi.query_scene_numbers_for_group(address)
|
|
32
|
+
print(f" scenes: {scenes}")
|
|
33
|
+
|
|
34
|
+
for scene in scenes:
|
|
35
|
+
label = await tpi.query_scene_label_for_group(address, scene)
|
|
36
|
+
print(f" Group {address.number} scene {scene} label: {label}")
|
|
37
|
+
|
|
38
|
+
gear = await tpi.query_control_gear_dali_addresses(ctrl)
|
|
39
|
+
print(f"Gear")
|
|
40
|
+
for gear in gear:
|
|
41
|
+
print(f" {gear.number}")
|
|
42
|
+
groups = await tpi.query_group_membership_by_address(gear)
|
|
43
|
+
for group in groups:
|
|
44
|
+
print(f" group: {group}")
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error during testing: {e}")
|
|
48
|
+
|
|
49
|
+
print("=" * 50)
|
|
50
|
+
print("Test completed!")
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
run_with_keyboard_interrupt(main)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test instance queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=False) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing instance queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
addresses = await tpi.query_dali_addresses_with_instances(ctrl, 0)
|
|
21
|
+
print(f"Addresses with instances:")
|
|
22
|
+
|
|
23
|
+
for address in addresses:
|
|
24
|
+
print(f" {address.number}")
|
|
25
|
+
|
|
26
|
+
label = await tpi.query_dali_device_label(address)
|
|
27
|
+
print(f" label: {label}")
|
|
28
|
+
|
|
29
|
+
operating_mode = await tpi.query_operating_mode_by_address(address)
|
|
30
|
+
print(f" operating mode: {operating_mode}")
|
|
31
|
+
|
|
32
|
+
instances = await tpi.query_instances_by_address(address)
|
|
33
|
+
# print(f" instances: {instances}")
|
|
34
|
+
|
|
35
|
+
for instance in instances:
|
|
36
|
+
|
|
37
|
+
instance_label = await tpi.query_dali_instance_label(instance)
|
|
38
|
+
print(f" {instance.number} - {instance_label}")
|
|
39
|
+
|
|
40
|
+
groups = await tpi.query_instance_groups(instance)
|
|
41
|
+
print(f" groups: {groups}")
|
|
42
|
+
|
|
43
|
+
fitting = await tpi.query_dali_instance_fitting_number(instance)
|
|
44
|
+
print(f" fitting: {fitting}")
|
|
45
|
+
|
|
46
|
+
occupancy_timers = await tpi.query_occupancy_instance_timers(instance)
|
|
47
|
+
print(f" occupancy timers: {occupancy_timers}")
|
|
48
|
+
|
|
49
|
+
last_known_led_state = await tpi.query_last_known_dali_button_led_state(instance)
|
|
50
|
+
print(f" last known led state: {last_known_led_state}")
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Error during testing: {e}")
|
|
54
|
+
|
|
55
|
+
print("=" * 50)
|
|
56
|
+
print("Test completed!")
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
run_with_keyboard_interrupt(main)
|
|
60
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from zencontrol import ZenControl, run_with_keyboard_interrupt
|
|
3
|
+
import yaml
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
async def main():
|
|
8
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
|
+
zi = ZenControl(print_traffic=False)
|
|
10
|
+
zi.add_controller(**config.get('zencontrol')[0])
|
|
11
|
+
await zi.start()
|
|
12
|
+
|
|
13
|
+
timer_start = time.time()
|
|
14
|
+
|
|
15
|
+
print("Profiles")
|
|
16
|
+
profiles = await zi.get_profiles()
|
|
17
|
+
for profile in profiles:
|
|
18
|
+
print(f" • {profile}")
|
|
19
|
+
|
|
20
|
+
print("Lights")
|
|
21
|
+
lights = await zi.get_lights()
|
|
22
|
+
for light in lights:
|
|
23
|
+
print(f" • {light}")
|
|
24
|
+
for group in light.groups:
|
|
25
|
+
print(f" • {group}")
|
|
26
|
+
|
|
27
|
+
print("Groups")
|
|
28
|
+
groups = await zi.get_groups()
|
|
29
|
+
for group in groups:
|
|
30
|
+
print(f" • {group}")
|
|
31
|
+
for light in group.lights:
|
|
32
|
+
print(f" • {light}")
|
|
33
|
+
|
|
34
|
+
print("Buttons")
|
|
35
|
+
buttons = await zi.get_buttons()
|
|
36
|
+
for button in buttons:
|
|
37
|
+
print(f" • {button}")
|
|
38
|
+
|
|
39
|
+
print("Motion sensors")
|
|
40
|
+
motion_sensors = await zi.get_motion_sensors()
|
|
41
|
+
for motion_sensor in motion_sensors:
|
|
42
|
+
print(f" • {motion_sensor}")
|
|
43
|
+
print(f" = {'occupied' if motion_sensor.occupied else 'not occupied'}")
|
|
44
|
+
|
|
45
|
+
print("System variables")
|
|
46
|
+
system_variables = await zi.get_system_variables()
|
|
47
|
+
for zsv in system_variables:
|
|
48
|
+
print(f" • {zsv}")
|
|
49
|
+
value = await zsv.get_value()
|
|
50
|
+
print(f" = {value}")
|
|
51
|
+
|
|
52
|
+
timer_end = time.time()
|
|
53
|
+
print(f"Time taken: {timer_end - timer_start} seconds")
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
run_with_keyboard_interrupt(main)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from zencontrol import ZenControl, ZenProfile, ZenGroup, ZenLight, ZenButton, ZenMotionSensor, ZenSystemVariable, ZenColour, run_with_keyboard_interrupt
|
|
3
|
+
import yaml
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
async def main():
|
|
8
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
|
+
zi = ZenControl(print_traffic=False)
|
|
10
|
+
zi.add_controller(**config.get('zencontrol')[0])
|
|
11
|
+
|
|
12
|
+
# Handlers
|
|
13
|
+
async def _zen_on_connect() -> None:
|
|
14
|
+
print(f"Connected to Zen")
|
|
15
|
+
|
|
16
|
+
async def _zen_on_disconnect() -> None:
|
|
17
|
+
print(f"Disconnected from Zen")
|
|
18
|
+
|
|
19
|
+
async def _zen_profile_change(profile: ZenProfile) -> None:
|
|
20
|
+
ms()
|
|
21
|
+
print(f"Profile Change Event - {profile}")
|
|
22
|
+
|
|
23
|
+
async def _zen_group_change(group: ZenGroup, level: int | None = None, colour: ZenColour | None = None, scene: int | None = None, discoordinated: bool = False) -> None:
|
|
24
|
+
ms()
|
|
25
|
+
print(f"Group Change Event - {group} level {level} colour {colour} scene {scene} {'discoordinated' if discoordinated else ''}")
|
|
26
|
+
|
|
27
|
+
async def _zen_light_change(light: ZenLight, level: int | None = None, colour: ZenColour | None = None, scene: int | None = None) -> None:
|
|
28
|
+
ms()
|
|
29
|
+
print(f"Light Change Event - {light} level {level} colour {colour} scene {scene}")
|
|
30
|
+
|
|
31
|
+
async def _zen_button_press(button: ZenButton) -> None:
|
|
32
|
+
ms()
|
|
33
|
+
print(f"Button Press Event - {button}")
|
|
34
|
+
|
|
35
|
+
async def _zen_button_long_press(button: ZenButton) -> None:
|
|
36
|
+
ms()
|
|
37
|
+
print(f"Button Long Press Event - {button}")
|
|
38
|
+
|
|
39
|
+
async def _zen_motion_event(sensor: ZenMotionSensor, occupied: bool) -> None:
|
|
40
|
+
ms()
|
|
41
|
+
print(f"Motion Event - {sensor} {'occupied' if occupied else 'not occupied'}")
|
|
42
|
+
|
|
43
|
+
async def _zen_system_variable_change(system_variable: ZenSystemVariable, value: int, changed: bool, by_me: bool) -> None:
|
|
44
|
+
ms()
|
|
45
|
+
print(f"System Variable Change - {system_variable} value {value} {'changed' if changed else 'not changed'} {'by me' if by_me else 'by someone else'}")
|
|
46
|
+
|
|
47
|
+
timevar = None
|
|
48
|
+
def ms():
|
|
49
|
+
nonlocal timevar
|
|
50
|
+
timevar = timevar or time.time()
|
|
51
|
+
msecs = (time.time() - timevar) * 1000
|
|
52
|
+
timevar = time.time()
|
|
53
|
+
print(f"{msecs:.1f} ms")
|
|
54
|
+
|
|
55
|
+
# Set up event callbacks
|
|
56
|
+
# zi.on_connect = _zen_on_connect
|
|
57
|
+
# zi.on_disconnect = _zen_on_disconnect
|
|
58
|
+
# zi.profile_change = _zen_profile_change
|
|
59
|
+
zi.group_change = _zen_group_change
|
|
60
|
+
zi.light_change = _zen_light_change
|
|
61
|
+
zi.button_press = _zen_button_press
|
|
62
|
+
# zi.button_long_press = _zen_button_long_press
|
|
63
|
+
# zi.motion_event = _zen_motion_event
|
|
64
|
+
# zi.system_variable_change = _zen_system_variable_change
|
|
65
|
+
|
|
66
|
+
# Start event monitoring
|
|
67
|
+
await zi.start()
|
|
68
|
+
|
|
69
|
+
print("Event monitoring started. Press Ctrl+C to stop.")
|
|
70
|
+
|
|
71
|
+
# Loop forever
|
|
72
|
+
try:
|
|
73
|
+
while True:
|
|
74
|
+
await asyncio.sleep(1)
|
|
75
|
+
except KeyboardInterrupt:
|
|
76
|
+
print("\nStopping event monitoring...")
|
|
77
|
+
await zi.stop()
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
run_with_keyboard_interrupt(main)
|
examples/live/leds.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from zencontrol import ZenProtocol, ZenController, ZenAddress, ZenAddressType, run_with_keyboard_interrupt
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
"""Test LED control queries"""
|
|
8
|
+
# Load configuration
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
10
|
+
|
|
11
|
+
# Create protocol and controller
|
|
12
|
+
async with ZenProtocol(print_traffic=False) as tpi:
|
|
13
|
+
ctrl = ZenController(protocol=tpi, **config.get('zencontrol')[0])
|
|
14
|
+
tpi.set_controllers([ctrl])
|
|
15
|
+
|
|
16
|
+
print("Testing LED control queries...")
|
|
17
|
+
print("=" * 50)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
address = ZenAddress(ctrl, ZenAddressType.ECD, 4) # Office
|
|
21
|
+
|
|
22
|
+
instances = await tpi.query_instances_by_address(address)
|
|
23
|
+
print(f" instances: {instances}")
|
|
24
|
+
|
|
25
|
+
for instance in instances:
|
|
26
|
+
|
|
27
|
+
instance_label = await tpi.query_dali_instance_label(instance, generic_if_none=True)
|
|
28
|
+
print(f" {instance.number} - {instance_label}")
|
|
29
|
+
|
|
30
|
+
last_known_led_state = await tpi.query_last_known_dali_button_led_state(instance)
|
|
31
|
+
print(f" last known led state: {last_known_led_state}")
|
|
32
|
+
|
|
33
|
+
set_led_state = await tpi.override_dali_button_led_state(instance, False)
|
|
34
|
+
print(f" set led state: {set_led_state}")
|
|
35
|
+
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error during testing: {e}")
|
|
38
|
+
|
|
39
|
+
print("=" * 50)
|
|
40
|
+
print("Test completed!")
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
run_with_keyboard_interrupt(main)
|
|
44
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import random
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
7
|
+
if str(ROOT) not in sys.path:
|
|
8
|
+
sys.path.insert(0, str(ROOT))
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from zencontrol.api.models import ZenAddress, ZenController
|
|
13
|
+
from zencontrol.api.protocol import ZenProtocol
|
|
14
|
+
from zencontrol.api.types import ZenAddressType, ZenEventCode
|
|
15
|
+
from zencontrol.utils import run_with_keyboard_interrupt
|
|
16
|
+
|
|
17
|
+
CONFIG_PATH = Path(__file__).resolve().parents[2] / "tests" / "config.yaml"
|
|
18
|
+
ECG_ADDRESS = 33
|
|
19
|
+
TIMEOUT_SECONDS = 5.0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def test_level_change_v2():
|
|
23
|
+
config = yaml.safe_load(CONFIG_PATH.read_text())
|
|
24
|
+
level = random.randint(100, 250)
|
|
25
|
+
event_received = asyncio.Event()
|
|
26
|
+
received: dict = {}
|
|
27
|
+
|
|
28
|
+
async with ZenProtocol(print_traffic=True) as tpi:
|
|
29
|
+
ctrl = ZenController(protocol=tpi, **config["zencontrol"][0])
|
|
30
|
+
tpi.set_controllers([ctrl])
|
|
31
|
+
|
|
32
|
+
original_process = tpi._process_zen_event
|
|
33
|
+
|
|
34
|
+
async def capture_level_change_v2(event):
|
|
35
|
+
if (
|
|
36
|
+
event.event_code == ZenEventCode.LEVEL_CHANGE_V2.value
|
|
37
|
+
and event.target == ECG_ADDRESS
|
|
38
|
+
):
|
|
39
|
+
received["event"] = event
|
|
40
|
+
event_received.set()
|
|
41
|
+
await original_process(event)
|
|
42
|
+
|
|
43
|
+
tpi._process_zen_event = capture_level_change_v2
|
|
44
|
+
|
|
45
|
+
await tpi.start_event_monitoring()
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
address = ZenAddress(ctrl, ZenAddressType.ECG, ECG_ADDRESS)
|
|
49
|
+
await tpi.dali_arc_level(address, level)
|
|
50
|
+
print(f"Set address {ECG_ADDRESS} to level {level}, waiting for LEVEL_CHANGE_V2...")
|
|
51
|
+
await asyncio.wait_for(event_received.wait(), timeout=TIMEOUT_SECONDS)
|
|
52
|
+
except asyncio.TimeoutError:
|
|
53
|
+
raise RuntimeError(
|
|
54
|
+
f"Timed out after {TIMEOUT_SECONDS}s waiting for LEVEL_CHANGE_V2 "
|
|
55
|
+
f"on address {ECG_ADDRESS} level {level}"
|
|
56
|
+
) from None
|
|
57
|
+
finally:
|
|
58
|
+
await tpi.stop_event_monitoring()
|
|
59
|
+
|
|
60
|
+
event = received["event"]
|
|
61
|
+
if event.event_code != ZenEventCode.LEVEL_CHANGE_V2.value:
|
|
62
|
+
raise RuntimeError(f"Expected LEVEL_CHANGE_V2, got event code {event.event_code}")
|
|
63
|
+
if event.target != ECG_ADDRESS:
|
|
64
|
+
raise RuntimeError(f"Expected target {ECG_ADDRESS}, got {event.target}")
|
|
65
|
+
|
|
66
|
+
current = event.payload[0] if len(event.payload) >= 1 else None
|
|
67
|
+
target = event.payload[1] if len(event.payload) >= 2 else None
|
|
68
|
+
print(f"LEVEL_CHANGE_V2 received: address {event.target}, current {current}, target {target}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
await test_level_change_v2()
|
|
73
|
+
print("Test passed.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
run_with_keyboard_interrupt(main)
|