zencontrol-python 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/PKG-INFO +29 -18
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/README.md +26 -13
- zencontrol_python-0.1.1/tests/test_controller.py → zencontrol_python-0.1.3/examples/live/controller.py +2 -1
- zencontrol_python-0.1.1/tests/test_devices.py → zencontrol_python-0.1.3/examples/live/devices.py +2 -1
- zencontrol_python-0.1.1/tests/test_events.py → zencontrol_python-0.1.3/examples/live/events.py +1 -1
- zencontrol_python-0.1.1/tests/test_gear.py → zencontrol_python-0.1.3/examples/live/gear.py +2 -1
- zencontrol_python-0.1.1/tests/test_groups.py → zencontrol_python-0.1.3/examples/live/groups.py +2 -1
- zencontrol_python-0.1.1/tests/test_instances.py → zencontrol_python-0.1.3/examples/live/instances.py +2 -1
- zencontrol_python-0.1.1/tests/test_interface.py → zencontrol_python-0.1.3/examples/live/interface.py +2 -1
- zencontrol_python-0.1.1/tests/test_interface_events.py → zencontrol_python-0.1.3/examples/live/interface_events.py +4 -4
- zencontrol_python-0.1.1/tests/test_leds.py → zencontrol_python-0.1.3/examples/live/leds.py +2 -1
- zencontrol_python-0.1.1/tests/test_level_change_v2.py → zencontrol_python-0.1.3/examples/live/level_change_v2.py +1 -1
- zencontrol_python-0.1.1/tests/test_multicast.py → zencontrol_python-0.1.3/examples/live/multicast.py +2 -1
- zencontrol_python-0.1.1/tests/test_profiles.py → zencontrol_python-0.1.3/examples/live/profiles.py +2 -1
- zencontrol_python-0.1.1/tests/test_variables.py → zencontrol_python-0.1.3/examples/live/variables.py +2 -1
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/examples/mqtt_bridge.py +7 -37
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/pyproject.toml +17 -6
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_entity_scope.py +7 -0
- zencontrol_python-0.1.3/tests/test_sim_events.py +206 -0
- zencontrol_python-0.1.3/tests/test_sim_interface.py +341 -0
- zencontrol_python-0.1.3/tests/test_sim_protocol.py +524 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_wire_resilience.py +118 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/__init__.py +4 -2
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/api/models.py +127 -98
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/api/protocol.py +199 -150
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/api/types.py +8 -6
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/exceptions.py +13 -2
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/interface/interface.py +177 -159
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/io/command.py +82 -58
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/io/event.py +13 -14
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/utils.py +4 -2
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/PKG-INFO +29 -18
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/SOURCES.txt +18 -15
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/LICENSE +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/examples/__init__.py +0 -0
- /zencontrol_python-0.1.1/tests/test_multicast_raw.py → /zencontrol_python-0.1.3/examples/live/multicast_raw.py +0 -0
- /zencontrol_python-0.1.1/tests/test_tpi_event_listener.py → /zencontrol_python-0.1.3/examples/live/tpi_event_listener.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/setup.cfg +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_discovery.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_event_listener_disconnect.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_lifecycle.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_reconnect.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/tests/test_sub_labels.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/api/__init__.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/interface/__init__.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/io/__init__.py +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol/py.typed +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/dependency_links.txt +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/entry_points.txt +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/requires.txt +0 -0
- {zencontrol_python-0.1.1 → zencontrol_python-0.1.3}/zencontrol_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zencontrol-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Python implementation of the Zencontrol TPI Advanced protocol for DALI lighting controllers.
|
|
5
5
|
Author: Simon Wright
|
|
6
6
|
License-Expression: MIT
|
|
@@ -12,13 +12,11 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
16
|
Classifier: Topic :: Home Automation
|
|
19
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
18
|
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.14
|
|
22
20
|
Description-Content-Type: text/markdown
|
|
23
21
|
License-File: LICENSE
|
|
24
22
|
Requires-Dist: colorama>=0.4.6
|
|
@@ -40,33 +38,46 @@ This is an implementation of the **Zencontrol TPI Advanced** protocol, written i
|
|
|
40
38
|
|
|
41
39
|
- zencontrol.io: Implementation of the raw TPI Advanced UDP packet specification;
|
|
42
40
|
- zencontrol.api: Implementation of most TPI Advanced API commands and events;
|
|
43
|
-
- zencontrol.interface: An opinionated abstraction layer suitable for integration into smart building control software. It provides methods, objects, and callbacks for managing lights, groups, profiles, buttons, motion sensors, and system variables.
|
|
41
|
+
- zencontrol.interface: An opinionated abstraction layer suitable for integration into smart building control software. It provides methods, objects, and callbacks for managing lights, groups, profiles, buttons, motion sensors, and system variables.
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
This library has now undergone validation in multiple environments. There is an extensive test suite, some of which uses
|
|
44
|
+
[zencontrol-simulator](https://github.com/sjwright/zencontrol-simulator), a nearly feature-complete simulator of zencontrol hardware.
|
|
45
|
+
A practical demonstration is [zencontrol-homeassistant](https://github.com/sjwright/zencontrol-homeassistant), a comprehensive Home Assistant integration.
|
|
46
46
|
|
|
47
47
|
## Requirements
|
|
48
48
|
|
|
49
|
-
* Python 3.
|
|
50
|
-
* Controller firmware 2.2.
|
|
49
|
+
* Python 3.14 (or later)
|
|
50
|
+
* Controller firmware 2.2.130 or later is strongly recommended (minimum 2.2.11 required)
|
|
51
51
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
This library is available on PyPI.
|
|
55
|
+
|
|
56
|
+
## Testing
|
|
57
|
+
|
|
58
|
+
Integration tests start [zencontrol-simulator](https://github.com/sjwright/zencontrol-simulator) on an ephemeral local port and exercise a real UDP TPI protocol path. Either install the simulator, or check it out as a sibling directory (`../zencontrol-simulator`); tests will pick it up automatically. Note that PyYAML is a simulator dependency.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -e ".[dev]"
|
|
62
|
+
pip install PyYAML
|
|
63
|
+
# optional if not using a sibling checkout:
|
|
64
|
+
# pip install -e ../zencontrol-simulator
|
|
65
|
+
pytest -m simulator
|
|
66
|
+
pytest -m "not simulator"
|
|
67
|
+
# or run everything:
|
|
68
|
+
pytest
|
|
69
|
+
```
|
|
56
70
|
|
|
57
71
|
## Limitations
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* Dealing with multiple controllers (I only have one controller)
|
|
62
|
-
* RGB+ and XY colour commands (I don't have any compatible lights)
|
|
63
|
-
* Numerical (absolute) instances (I don't have any such ECDs)
|
|
64
|
-
* Event filtering (I haven't tested it)
|
|
73
|
+
* RGB+ and XY colour commands are not tested (I don't have any compatible lights)
|
|
74
|
+
* Numerical (absolute) instances are not tested (I don't have any such ECDs)
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
## Out of scope
|
|
67
77
|
|
|
68
78
|
* Any commands involving DMX, Control4, or virtual instances (I don't have licenses for any of these so I couldn't test them even if I wanted to, but the scaffolding is there if anyone wishes to add support)
|
|
69
79
|
* Any commands described in the documentation as "legacy" (they aren't useful)
|
|
80
|
+
|
|
70
81
|
## TPI Advanced wishlist
|
|
71
82
|
|
|
72
83
|
* Command to return a controller's MAC address used for multicast packets _(There are other ways to get or infer the MAC access, but they're unreliable.)_
|
|
@@ -4,33 +4,46 @@ This is an implementation of the **Zencontrol TPI Advanced** protocol, written i
|
|
|
4
4
|
|
|
5
5
|
- zencontrol.io: Implementation of the raw TPI Advanced UDP packet specification;
|
|
6
6
|
- zencontrol.api: Implementation of most TPI Advanced API commands and events;
|
|
7
|
-
- zencontrol.interface: An opinionated abstraction layer suitable for integration into smart building control software. It provides methods, objects, and callbacks for managing lights, groups, profiles, buttons, motion sensors, and system variables.
|
|
7
|
+
- zencontrol.interface: An opinionated abstraction layer suitable for integration into smart building control software. It provides methods, objects, and callbacks for managing lights, groups, profiles, buttons, motion sensors, and system variables.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This library has now undergone validation in multiple environments. There is an extensive test suite, some of which uses
|
|
10
|
+
[zencontrol-simulator](https://github.com/sjwright/zencontrol-simulator), a nearly feature-complete simulator of zencontrol hardware.
|
|
11
|
+
A practical demonstration is [zencontrol-homeassistant](https://github.com/sjwright/zencontrol-homeassistant), a comprehensive Home Assistant integration.
|
|
10
12
|
|
|
11
13
|
## Requirements
|
|
12
14
|
|
|
13
|
-
* Python 3.
|
|
14
|
-
* Controller firmware 2.2.
|
|
15
|
+
* Python 3.14 (or later)
|
|
16
|
+
* Controller firmware 2.2.130 or later is strongly recommended (minimum 2.2.11 required)
|
|
15
17
|
|
|
16
18
|
## Install
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
This library is available on PyPI.
|
|
21
|
+
|
|
22
|
+
## Testing
|
|
23
|
+
|
|
24
|
+
Integration tests start [zencontrol-simulator](https://github.com/sjwright/zencontrol-simulator) on an ephemeral local port and exercise a real UDP TPI protocol path. Either install the simulator, or check it out as a sibling directory (`../zencontrol-simulator`); tests will pick it up automatically. Note that PyYAML is a simulator dependency.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
pip install PyYAML
|
|
29
|
+
# optional if not using a sibling checkout:
|
|
30
|
+
# pip install -e ../zencontrol-simulator
|
|
31
|
+
pytest -m simulator
|
|
32
|
+
pytest -m "not simulator"
|
|
33
|
+
# or run everything:
|
|
34
|
+
pytest
|
|
35
|
+
```
|
|
20
36
|
|
|
21
37
|
## Limitations
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* Dealing with multiple controllers (I only have one controller)
|
|
26
|
-
* RGB+ and XY colour commands (I don't have any compatible lights)
|
|
27
|
-
* Numerical (absolute) instances (I don't have any such ECDs)
|
|
28
|
-
* Event filtering (I haven't tested it)
|
|
39
|
+
* RGB+ and XY colour commands are not tested (I don't have any compatible lights)
|
|
40
|
+
* Numerical (absolute) instances are not tested (I don't have any such ECDs)
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
## Out of scope
|
|
31
43
|
|
|
32
44
|
* Any commands involving DMX, Control4, or virtual instances (I don't have licenses for any of these so I couldn't test them even if I wanted to, but the scaffolding is there if anyone wishes to add support)
|
|
33
45
|
* Any commands described in the documentation as "legacy" (they aren't useful)
|
|
46
|
+
|
|
34
47
|
## TPI Advanced wishlist
|
|
35
48
|
|
|
36
49
|
* Command to return a controller's MAC address used for multicast packets _(There are other ways to get or infer the MAC access, but they're unreliable.)_
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test the async ZenProtocol with controller queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=True) as tpi:
|
zencontrol_python-0.1.1/tests/test_devices.py → zencontrol_python-0.1.3/examples/live/devices.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test DALI device queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=False) as tpi:
|
zencontrol_python-0.1.1/tests/test_events.py → zencontrol_python-0.1.3/examples/live/events.py
RENAMED
|
@@ -13,7 +13,7 @@ sys.stdout.reconfigure(line_buffering=True)
|
|
|
13
13
|
|
|
14
14
|
from zencontrol import ZenProtocol, ZenController, ZenAddress, ZenInstance, run_with_keyboard_interrupt
|
|
15
15
|
|
|
16
|
-
CONFIG_PATH = Path(__file__).
|
|
16
|
+
CONFIG_PATH = Path(__file__).resolve().parents[2] / "tests" / "config.yaml"
|
|
17
17
|
|
|
18
18
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
|
|
19
19
|
logger = logging.getLogger("test_events")
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test control gear queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=False) as tpi:
|
zencontrol_python-0.1.1/tests/test_groups.py → zencontrol_python-0.1.3/examples/live/groups.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test group queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=True) as tpi:
|
zencontrol_python-0.1.1/tests/test_instances.py → zencontrol_python-0.1.3/examples/live/instances.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test instance queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=False) as tpi:
|
zencontrol_python-0.1.1/tests/test_interface.py → zencontrol_python-0.1.3/examples/live/interface.py
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from zencontrol import ZenControl, run_with_keyboard_interrupt
|
|
3
3
|
import yaml
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
import time
|
|
5
6
|
|
|
6
7
|
async def main():
|
|
7
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
8
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
8
9
|
zi = ZenControl(print_traffic=False)
|
|
9
10
|
zi.add_controller(**config.get('zencontrol')[0])
|
|
10
11
|
await zi.start()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from zencontrol import ZenControl, ZenProfile, ZenGroup, ZenLight, ZenButton, ZenMotionSensor, ZenSystemVariable, ZenColour, run_with_keyboard_interrupt
|
|
3
3
|
import yaml
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
import time
|
|
5
|
-
from typing import Optional
|
|
6
6
|
|
|
7
7
|
async def main():
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
8
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
9
|
zi = ZenControl(print_traffic=False)
|
|
10
10
|
zi.add_controller(**config.get('zencontrol')[0])
|
|
11
11
|
|
|
@@ -20,11 +20,11 @@ async def main():
|
|
|
20
20
|
ms()
|
|
21
21
|
print(f"Profile Change Event - {profile}")
|
|
22
22
|
|
|
23
|
-
async def _zen_group_change(group: ZenGroup, level:
|
|
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
24
|
ms()
|
|
25
25
|
print(f"Group Change Event - {group} level {level} colour {colour} scene {scene} {'discoordinated' if discoordinated else ''}")
|
|
26
26
|
|
|
27
|
-
async def _zen_light_change(light: ZenLight, level:
|
|
27
|
+
async def _zen_light_change(light: ZenLight, level: int | None = None, colour: ZenColour | None = None, scene: int | None = None) -> None:
|
|
28
28
|
ms()
|
|
29
29
|
print(f"Light Change Event - {light} level {level} colour {colour} scene {scene}")
|
|
30
30
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, ZenAddress, ZenAddressType, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test LED control queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=False) as tpi:
|
|
@@ -14,7 +14,7 @@ from zencontrol.api.protocol import ZenProtocol
|
|
|
14
14
|
from zencontrol.api.types import ZenAddressType, ZenEventCode
|
|
15
15
|
from zencontrol.utils import run_with_keyboard_interrupt
|
|
16
16
|
|
|
17
|
-
CONFIG_PATH = Path(__file__).
|
|
17
|
+
CONFIG_PATH = Path(__file__).resolve().parents[2] / "tests" / "config.yaml"
|
|
18
18
|
ECG_ADDRESS = 33
|
|
19
19
|
TIMEOUT_SECONDS = 5.0
|
|
20
20
|
|
zencontrol_python-0.1.1/tests/test_multicast.py → zencontrol_python-0.1.3/examples/live/multicast.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, ZenAddress, ZenInstance, ZenEventMode, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test multicast event monitoring"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=True, unicast=False) as tpi:
|
zencontrol_python-0.1.1/tests/test_profiles.py → zencontrol_python-0.1.3/examples/live/profiles.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test profile queries"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=False) as tpi:
|
zencontrol_python-0.1.1/tests/test_variables.py → zencontrol_python-0.1.3/examples/live/variables.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import yaml
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from zencontrol import ZenProtocol, ZenController, run_with_keyboard_interrupt
|
|
4
5
|
|
|
5
6
|
async def main():
|
|
6
7
|
"""Test system variable queries and sets"""
|
|
7
8
|
# Load configuration
|
|
8
|
-
config = yaml.safe_load(open("tests/config.yaml"))
|
|
9
|
+
config = yaml.safe_load(open(Path(__file__).resolve().parents[2] / "tests" / "config.yaml"))
|
|
9
10
|
|
|
10
11
|
# Create protocol and controller
|
|
11
12
|
async with ZenProtocol(print_traffic=True) as tpi:
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import yaml
|
|
6
6
|
import re
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any
|
|
9
9
|
import zencontrol
|
|
10
10
|
from zencontrol import ZenController, ZenProtocol, ZenClient, ZenColour, ZenColourType, ZenProfile, ZenLight, ZenGroup, ZenButton, ZenMotionSensor, ZenSystemVariable, ZenTimeoutError, ZenAddressType
|
|
11
11
|
from zencontrol.api.types import Const as ApiConst
|
|
@@ -157,36 +157,6 @@ class ZenMQTTBridge:
|
|
|
157
157
|
pickle.dump(self.zen.cache, f)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
"""
|
|
162
|
-
controllers:
|
|
163
|
-
- id: 1
|
|
164
|
-
name: zen1
|
|
165
|
-
label: Zencontrol 1
|
|
166
|
-
groups:
|
|
167
|
-
- id: 0
|
|
168
|
-
name: All Lights
|
|
169
|
-
hide_group: True
|
|
170
|
-
hide_members: True
|
|
171
|
-
- id: 1
|
|
172
|
-
name: Ground
|
|
173
|
-
lights:
|
|
174
|
-
- ecg: 14
|
|
175
|
-
name: Lounge NW
|
|
176
|
-
room: Downstairs
|
|
177
|
-
groups:
|
|
178
|
-
- 15
|
|
179
|
-
- ecg: 21
|
|
180
|
-
name: Guest NE
|
|
181
|
-
room: Guest Bedroom
|
|
182
|
-
groups:
|
|
183
|
-
- 15
|
|
184
|
-
- ecg: 42
|
|
185
|
-
name: Garage
|
|
186
|
-
override_name: Garage Downlights
|
|
187
|
-
room: Garage
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
160
|
clist = []
|
|
191
161
|
for c in sorted(self.control, key=lambda x: x.id):
|
|
192
162
|
c: ZenController
|
|
@@ -844,9 +814,9 @@ class ZenMQTTBridge:
|
|
|
844
814
|
async def _mqtt_light_change(self, light: ZenLight|ZenGroup, payload: dict[str, Any]) -> None:
|
|
845
815
|
addr = light.address
|
|
846
816
|
ctrl = addr.controller
|
|
847
|
-
state:
|
|
848
|
-
brightness:
|
|
849
|
-
mireds:
|
|
817
|
+
state: str | None = payload.get("state", None)
|
|
818
|
+
brightness: int | None = payload.get("brightness", None)
|
|
819
|
+
mireds: int | None = payload.get("color_temp", None)
|
|
850
820
|
|
|
851
821
|
# If brightness or temperature is set
|
|
852
822
|
if brightness is not None or mireds is not None:
|
|
@@ -865,7 +835,7 @@ class ZenMQTTBridge:
|
|
|
865
835
|
self.logger.info(f"♥️💡 Command from HA: {ctrl.name} turning gear {addr.number} ON")
|
|
866
836
|
await light.on()
|
|
867
837
|
|
|
868
|
-
async def _zen_light_change(self, light: ZenLight, level:
|
|
838
|
+
async def _zen_light_change(self, light: ZenLight, level: int | None = None, colour: ZenColour | None = None, scene: int | None = None) -> None:
|
|
869
839
|
typestr = "group" if light.address.type == ZenAddressType.GROUP else "light"
|
|
870
840
|
emoji = "👥" if light.address.type == ZenAddressType.GROUP else "💡"
|
|
871
841
|
self.logger.info(f"🩵{emoji} Event from Zen: {typestr} {light.address.number} level {level if level is not None else '--'} colour {colour if colour is not None else '--'} scene {scene if scene is not None else '--'}")
|
|
@@ -960,7 +930,7 @@ class ZenMQTTBridge:
|
|
|
960
930
|
|
|
961
931
|
# mqtt group light change calls _mqtt_light_change
|
|
962
932
|
|
|
963
|
-
async def _zen_group_change(self, group: ZenGroup, level:
|
|
933
|
+
async def _zen_group_change(self, group: ZenGroup, level: int | None = None, colour: ZenColour | None = None, scene: int | None = None, discoordinated: bool | None = None) -> None:
|
|
964
934
|
select_mqtt_topic = group.client_data.get("select", {}).get('mqtt_topic', None)
|
|
965
935
|
|
|
966
936
|
# Get the scene label for the ID from the group
|
|
@@ -1139,4 +1109,4 @@ async def main():
|
|
|
1139
1109
|
await bridge.stop()
|
|
1140
1110
|
|
|
1141
1111
|
if __name__ == "__main__":
|
|
1142
|
-
asyncio.run(main())
|
|
1112
|
+
asyncio.run(main())
|
|
@@ -7,7 +7,7 @@ name = "zencontrol-python"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Python implementation of the Zencontrol TPI Advanced protocol for DALI lighting controllers."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [
|
|
@@ -19,9 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"Operating System :: OS Independent",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.
|
|
23
|
-
"Programming Language :: Python :: 3.12",
|
|
24
|
-
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
25
23
|
"Topic :: Home Automation",
|
|
26
24
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
25
|
"Typing :: Typed",
|
|
@@ -43,6 +41,9 @@ mqtt = [
|
|
|
43
41
|
"aiomqtt>=2.1.0",
|
|
44
42
|
"PyYAML>=6.0",
|
|
45
43
|
]
|
|
44
|
+
# Simulator integration tests (tests/test_sim_*.py) need zencontrol-simulator
|
|
45
|
+
# (pip install -e ../zencontrol-simulator, or a sibling ../zencontrol-simulator
|
|
46
|
+
# checkout) plus PyYAML.
|
|
46
47
|
|
|
47
48
|
[project.scripts]
|
|
48
49
|
zencontrol-mqtt = "examples.mqtt_bridge:main"
|
|
@@ -64,7 +65,7 @@ zencontrol = ["py.typed"]
|
|
|
64
65
|
|
|
65
66
|
[tool.black]
|
|
66
67
|
line-length = 88
|
|
67
|
-
target-version = ['
|
|
68
|
+
target-version = ['py314']
|
|
68
69
|
include = '\.pyi?$'
|
|
69
70
|
extend-exclude = '''
|
|
70
71
|
/(
|
|
@@ -81,7 +82,7 @@ extend-exclude = '''
|
|
|
81
82
|
'''
|
|
82
83
|
|
|
83
84
|
[tool.mypy]
|
|
84
|
-
python_version = "3.
|
|
85
|
+
python_version = "3.14"
|
|
85
86
|
warn_return_any = true
|
|
86
87
|
warn_unused_configs = true
|
|
87
88
|
disallow_untyped_defs = true
|
|
@@ -102,3 +103,13 @@ python_classes = ["Test*"]
|
|
|
102
103
|
python_functions = ["test_*"]
|
|
103
104
|
addopts = "-v --tb=short"
|
|
104
105
|
asyncio_mode = "auto"
|
|
106
|
+
markers = [
|
|
107
|
+
"simulator: integration tests that start zencontrol-simulator (installed or sibling ../zencontrol-simulator checkout)",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff]
|
|
111
|
+
target-version = "py314"
|
|
112
|
+
line-length = 88
|
|
113
|
+
|
|
114
|
+
[tool.ruff.lint]
|
|
115
|
+
select = ["ASYNC", "B", "E", "F", "I", "UP"]
|
|
@@ -77,6 +77,9 @@ def test_controller_same_name_same_protocol_is_singleton() -> None:
|
|
|
77
77
|
host="127.0.0.1",
|
|
78
78
|
port=5108,
|
|
79
79
|
)
|
|
80
|
+
a.client = object() # type: ignore[assignment]
|
|
81
|
+
a.version = "1.2.3"
|
|
82
|
+
a.startup_complete = True
|
|
80
83
|
b = ZenController(
|
|
81
84
|
protocol=zen.protocol,
|
|
82
85
|
id=2,
|
|
@@ -88,4 +91,8 @@ def test_controller_same_name_same_protocol_is_singleton() -> None:
|
|
|
88
91
|
assert a is b
|
|
89
92
|
assert a.label == "Second"
|
|
90
93
|
assert a.host == "10.0.0.1"
|
|
94
|
+
# Re-construct must not wipe live transport / interview state
|
|
95
|
+
assert a.client is not None
|
|
96
|
+
assert a.version == "1.2.3"
|
|
97
|
+
assert a.startup_complete is True
|
|
91
98
|
zen.clear_entity_caches()
|