python-rako-2025 0.0.1__tar.gz → 0.2.0__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.
- {python_rako_2025-0.0.1/python_rako_2025.egg-info → python_rako_2025-0.2.0}/PKG-INFO +1 -1
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/pyproject.toml +3 -2
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/__init__.py +3 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/__version__.py +2 -1
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/bridge.py +73 -18
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/helpers.py +1 -1
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/model.py +24 -1
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0/python_rako_2025.egg-info}/PKG-INFO +1 -1
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/setup.py +6 -3
- python_rako_2025-0.2.0/tests/test_bridge.py +225 -0
- python_rako_2025-0.0.1/tests/test_bridge.py +0 -99
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/LICENSE +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/README.md +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/const.py +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/exceptions.py +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako/py.typed +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/SOURCES.txt +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/dependency_links.txt +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/not-zip-safe +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/requires.txt +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/top_level.txt +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/setup.cfg +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/tests/test_helpers.py +0 -0
- {python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/tests/test_model.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-rako-2025"
|
|
7
|
-
version = "0.0
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Asynchronous Python client for Rako Controls Lighting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = ["rako", "controls", "api", "async", "client"]
|
|
@@ -81,6 +81,7 @@ warn_unused_ignores = true
|
|
|
81
81
|
warn_return_any = true
|
|
82
82
|
warn_unreachable = true
|
|
83
83
|
strict_equality = true
|
|
84
|
+
enable_error_code = ["ignore-without-code"]
|
|
84
85
|
|
|
85
86
|
[tool.pytest.ini_options]
|
|
86
|
-
asyncio_mode = "auto"
|
|
87
|
+
asyncio_mode = "auto"
|
|
@@ -15,14 +15,17 @@ from python_rako.model import ( # noqa
|
|
|
15
15
|
BridgeInfo,
|
|
16
16
|
ChannelLight,
|
|
17
17
|
ChannelStatusMessage,
|
|
18
|
+
ChannelVentilation,
|
|
18
19
|
LevelCache,
|
|
19
20
|
LevelCacheItem,
|
|
20
21
|
Light,
|
|
21
22
|
RoomChannel,
|
|
22
23
|
RoomLight,
|
|
24
|
+
RoomVentilation,
|
|
23
25
|
SceneCache,
|
|
24
26
|
SceneStatusMessage,
|
|
25
27
|
UnsupportedMessage,
|
|
28
|
+
Ventilation,
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import AsyncGenerator, Generator
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
import aiohttp
|
|
8
9
|
import xmltodict
|
|
@@ -24,6 +25,7 @@ from python_rako.helpers import (
|
|
|
24
25
|
from python_rako.model import (
|
|
25
26
|
BridgeInfo,
|
|
26
27
|
ChannelLight,
|
|
28
|
+
ChannelVentilation,
|
|
27
29
|
CommandHTTP,
|
|
28
30
|
CommandLevelHTTP,
|
|
29
31
|
CommandSceneHTTP,
|
|
@@ -32,7 +34,9 @@ from python_rako.model import (
|
|
|
32
34
|
LevelCache,
|
|
33
35
|
Light,
|
|
34
36
|
RoomLight,
|
|
37
|
+
RoomVentilation,
|
|
35
38
|
SceneCache,
|
|
39
|
+
Ventilation,
|
|
36
40
|
)
|
|
37
41
|
|
|
38
42
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -158,11 +162,26 @@ class Bridge:
|
|
|
158
162
|
|
|
159
163
|
async def discover_lights(
|
|
160
164
|
self, session: aiohttp.ClientSession
|
|
161
|
-
) -> AsyncGenerator[Light
|
|
165
|
+
) -> AsyncGenerator[Light]:
|
|
162
166
|
rako_xml = await self.get_rako_xml(session)
|
|
163
167
|
for light in self.get_lights_from_discovery_xml(rako_xml):
|
|
164
168
|
yield light
|
|
165
169
|
|
|
170
|
+
async def discover_ventilation(
|
|
171
|
+
self, session: aiohttp.ClientSession
|
|
172
|
+
) -> AsyncGenerator[Ventilation]:
|
|
173
|
+
rako_xml = await self.get_rako_xml(session)
|
|
174
|
+
for device in self.get_devices_from_discovery_xml(rako_xml, "Ventilation"):
|
|
175
|
+
if isinstance(device, (RoomVentilation, ChannelVentilation)):
|
|
176
|
+
yield device
|
|
177
|
+
|
|
178
|
+
async def discover_all_devices(
|
|
179
|
+
self, session: aiohttp.ClientSession
|
|
180
|
+
) -> AsyncGenerator[Light | Ventilation]:
|
|
181
|
+
rako_xml = await self.get_rako_xml(session)
|
|
182
|
+
for device in self.get_devices_from_discovery_xml(rako_xml):
|
|
183
|
+
yield device
|
|
184
|
+
|
|
166
185
|
async def get_info(self, session: aiohttp.ClientSession) -> BridgeInfo:
|
|
167
186
|
try:
|
|
168
187
|
rako_xml = await self.get_rako_xml(session)
|
|
@@ -192,18 +211,43 @@ class Bridge:
|
|
|
192
211
|
)
|
|
193
212
|
|
|
194
213
|
@staticmethod
|
|
195
|
-
def get_lights_from_discovery_xml(xml: str) -> Generator[Light
|
|
196
|
-
|
|
214
|
+
def get_lights_from_discovery_xml(xml: str) -> Generator[Light]:
|
|
215
|
+
for device in Bridge.get_devices_from_discovery_xml(xml, "Lights"):
|
|
216
|
+
if isinstance(device, (RoomLight, ChannelLight)):
|
|
217
|
+
yield device
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def get_all_devices_from_discovery_xml(xml: str) -> Generator[Light | Ventilation]:
|
|
221
|
+
"""Get all devices (lights and ventilation) from discovery XML."""
|
|
222
|
+
return Bridge.get_devices_from_discovery_xml(xml)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def get_devices_from_discovery_xml(
|
|
226
|
+
xml: str, device_types: str | list[str] | None = None
|
|
227
|
+
) -> Generator[Light | Ventilation]:
|
|
228
|
+
# Handle different input types for backward compatibility
|
|
229
|
+
if device_types is None or device_types == "All":
|
|
230
|
+
target_types = {"Lights", "Ventilation"}
|
|
231
|
+
elif isinstance(device_types, str):
|
|
232
|
+
target_types = {device_types}
|
|
233
|
+
else:
|
|
234
|
+
target_types = set(device_types)
|
|
235
|
+
|
|
236
|
+
xml_dict = xmltodict.parse(xml, force_list={"Room"})
|
|
197
237
|
for room in xml_dict["rako"]["rooms"]["Room"]:
|
|
198
238
|
room_id = int(room["@id"])
|
|
199
239
|
room_type = room.get("Type", "Lights")
|
|
200
|
-
if room_type
|
|
201
|
-
_LOGGER.info(
|
|
202
|
-
"Unsupported room type. room_id=%s room_type=%s", room_id, room_type
|
|
203
|
-
)
|
|
240
|
+
if room_type not in target_types:
|
|
204
241
|
continue
|
|
205
242
|
room_title = room["Title"]
|
|
206
|
-
|
|
243
|
+
|
|
244
|
+
# Yield room-level device
|
|
245
|
+
if room_type == "Lights":
|
|
246
|
+
yield RoomLight(room_id, room_title)
|
|
247
|
+
elif room_type == "Ventilation":
|
|
248
|
+
yield RoomVentilation(room_id, room_title)
|
|
249
|
+
|
|
250
|
+
# Yield channel-level devices
|
|
207
251
|
channels_section = room.get("Channel", [])
|
|
208
252
|
channels = (
|
|
209
253
|
channels_section
|
|
@@ -215,14 +259,25 @@ class Bridge:
|
|
|
215
259
|
channel_type = channel.get("type", "Default")
|
|
216
260
|
channel_name = channel["Name"]
|
|
217
261
|
channel_levels = channel["Levels"]
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
262
|
+
|
|
263
|
+
if room_type == "Lights":
|
|
264
|
+
yield ChannelLight(
|
|
265
|
+
room_id,
|
|
266
|
+
room_title,
|
|
267
|
+
channel_id,
|
|
268
|
+
channel_type,
|
|
269
|
+
channel_name,
|
|
270
|
+
channel_levels,
|
|
271
|
+
)
|
|
272
|
+
elif room_type == "Ventilation":
|
|
273
|
+
yield ChannelVentilation(
|
|
274
|
+
room_id,
|
|
275
|
+
room_title,
|
|
276
|
+
channel_id,
|
|
277
|
+
channel_type,
|
|
278
|
+
channel_name,
|
|
279
|
+
channel_levels,
|
|
280
|
+
)
|
|
226
281
|
|
|
227
282
|
async def next_pushed_message(self, dg_listener: DatagramServer) -> Any | None:
|
|
228
283
|
resp = await dg_listener.recv()
|
|
@@ -241,7 +296,7 @@ class Bridge:
|
|
|
241
296
|
|
|
242
297
|
async def get_cache_state(
|
|
243
298
|
self, cache_type: RequestType = RequestType.SCENE_LEVEL_CACHE
|
|
244
|
-
) ->
|
|
299
|
+
) -> tuple[LevelCache, SceneCache]:
|
|
245
300
|
scene_cache = SceneCache()
|
|
246
301
|
level_cache = LevelCache()
|
|
247
302
|
async with get_dg_commander(self.host, self.port) as dg_client:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
4
5
|
from contextlib import asynccontextmanager
|
|
5
|
-
from typing import AsyncIterator
|
|
6
6
|
|
|
7
7
|
import asyncio_dgram
|
|
8
8
|
from asyncio_dgram.aio import DatagramClient, DatagramServer
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Iterable
|
|
3
3
|
|
|
4
4
|
from python_rako.const import CommandType, MessageType
|
|
5
5
|
|
|
@@ -33,6 +33,29 @@ class ChannelLight(Light):
|
|
|
33
33
|
channel_levels: str
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
@dataclass
|
|
37
|
+
class Ventilation:
|
|
38
|
+
room_id: int
|
|
39
|
+
room_title: str
|
|
40
|
+
channel_id: int
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def room_channel(self) -> RoomChannel:
|
|
44
|
+
return RoomChannel(self.room_id, self.channel_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class RoomVentilation(Ventilation):
|
|
49
|
+
channel_id: int = 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ChannelVentilation(Ventilation):
|
|
54
|
+
channel_type: str
|
|
55
|
+
channel_name: str
|
|
56
|
+
channel_levels: str
|
|
57
|
+
|
|
58
|
+
|
|
36
59
|
@dataclass(frozen=True)
|
|
37
60
|
class BridgeInfo:
|
|
38
61
|
version: str
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import os
|
|
2
|
+
|
|
2
3
|
from setuptools import find_packages, setup
|
|
3
4
|
|
|
5
|
+
|
|
4
6
|
def read(*parts):
|
|
5
7
|
"""Read file."""
|
|
6
8
|
filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)
|
|
7
|
-
with open(filename, encoding="utf-8"
|
|
9
|
+
with open(filename, encoding="utf-8") as fp:
|
|
8
10
|
return fp.read()
|
|
9
11
|
|
|
12
|
+
|
|
10
13
|
with open("README.md") as readme_file:
|
|
11
14
|
readme = readme_file.read()
|
|
12
15
|
|
|
13
16
|
setup(
|
|
14
17
|
name="python-rako-2025",
|
|
15
|
-
version="0.0
|
|
18
|
+
version="0.2.0",
|
|
16
19
|
license="MIT",
|
|
17
20
|
url="https://github.com/simonleigh/python-rako",
|
|
18
21
|
author="Simon Leigh",
|
|
@@ -49,4 +52,4 @@ setup(
|
|
|
49
52
|
"Development Status :: 4 - Beta",
|
|
50
53
|
],
|
|
51
54
|
python_requires=">=3.10",
|
|
52
|
-
)
|
|
55
|
+
)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from python_rako.bridge import Bridge
|
|
2
|
+
from python_rako.model import (
|
|
3
|
+
BridgeInfo,
|
|
4
|
+
ChannelLight,
|
|
5
|
+
ChannelVentilation,
|
|
6
|
+
RoomLight,
|
|
7
|
+
RoomVentilation,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_get_lights_from_discovery_xml(rako_xml):
|
|
12
|
+
lights = Bridge.get_lights_from_discovery_xml(rako_xml)
|
|
13
|
+
|
|
14
|
+
expected_lights = [
|
|
15
|
+
RoomLight(room_id=5, room_title="Living Room", channel_id=0),
|
|
16
|
+
ChannelLight(
|
|
17
|
+
room_id=5,
|
|
18
|
+
room_title="Living Room",
|
|
19
|
+
channel_id=1,
|
|
20
|
+
channel_type="Default",
|
|
21
|
+
channel_name="Downlights",
|
|
22
|
+
channel_levels="FF347F3F000000000000000000000000",
|
|
23
|
+
),
|
|
24
|
+
ChannelLight(
|
|
25
|
+
room_id=5,
|
|
26
|
+
room_title="Living Room",
|
|
27
|
+
channel_id=2,
|
|
28
|
+
channel_type="Default",
|
|
29
|
+
channel_name="Kitchen Downlights",
|
|
30
|
+
channel_levels="FF337F3F000000000000000000000000",
|
|
31
|
+
),
|
|
32
|
+
RoomLight(room_id=9, room_title="Bedroom 1", channel_id=0),
|
|
33
|
+
ChannelLight(
|
|
34
|
+
room_id=9,
|
|
35
|
+
room_title="Bedroom 1",
|
|
36
|
+
channel_id=1,
|
|
37
|
+
channel_type="Default",
|
|
38
|
+
channel_name="Downlights",
|
|
39
|
+
channel_levels="FFBF7F25000000000000000000000000",
|
|
40
|
+
),
|
|
41
|
+
ChannelLight(
|
|
42
|
+
room_id=9,
|
|
43
|
+
room_title="Bedroom 1",
|
|
44
|
+
channel_id=2,
|
|
45
|
+
channel_type="Default",
|
|
46
|
+
channel_name="LED",
|
|
47
|
+
channel_levels="FFBF7F00000000000000000000000000",
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
assert list(lights) == expected_lights
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_get_bridge_info_from_discovery_xml(rako_xml):
|
|
54
|
+
info = Bridge.get_bridge_info_from_discovery_xml(rako_xml)
|
|
55
|
+
|
|
56
|
+
expected_info = BridgeInfo(
|
|
57
|
+
version="2.4.0 RA",
|
|
58
|
+
buildDate="Nov 17 2017 10:01:01",
|
|
59
|
+
hostName="RAKOBRIDGE",
|
|
60
|
+
hostIP="someip",
|
|
61
|
+
hostMAC="somemac",
|
|
62
|
+
hwStatus="05",
|
|
63
|
+
dbVersion="-31",
|
|
64
|
+
requirepassword=None,
|
|
65
|
+
passhash="NAN",
|
|
66
|
+
charset="UTF-8",
|
|
67
|
+
)
|
|
68
|
+
assert info == expected_info
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_get_bridge_info_from_discovery_xml2(rako_xml2):
|
|
72
|
+
info = Bridge.get_bridge_info_from_discovery_xml(rako_xml2)
|
|
73
|
+
|
|
74
|
+
expected_info = BridgeInfo(
|
|
75
|
+
version=None,
|
|
76
|
+
buildDate=None,
|
|
77
|
+
hostName=None,
|
|
78
|
+
hostIP=None,
|
|
79
|
+
hostMAC=None,
|
|
80
|
+
hwStatus=None,
|
|
81
|
+
dbVersion=None,
|
|
82
|
+
requirepassword=None,
|
|
83
|
+
passhash="NAN",
|
|
84
|
+
charset=None,
|
|
85
|
+
)
|
|
86
|
+
assert info == expected_info
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_get_lights_from_discovery_xml2(rako_xml2):
|
|
90
|
+
lights = Bridge.get_lights_from_discovery_xml(rako_xml2)
|
|
91
|
+
|
|
92
|
+
expected_lights = [
|
|
93
|
+
RoomLight(room_id=112, room_title="Bedroom 1", channel_id=0),
|
|
94
|
+
ChannelLight(
|
|
95
|
+
room_id=112,
|
|
96
|
+
room_title="Bedroom 1",
|
|
97
|
+
channel_id=1,
|
|
98
|
+
channel_type="Default",
|
|
99
|
+
channel_name="Ceiling Light",
|
|
100
|
+
channel_levels="FFBF7F3F000000000000000000000000",
|
|
101
|
+
),
|
|
102
|
+
RoomLight(room_id=108, room_title="Master Ensuite", channel_id=0),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
assert list(lights) == expected_lights
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_get_ventilation_from_discovery_xml(rako_xml3):
|
|
109
|
+
ventilation_devices = Bridge.get_devices_from_discovery_xml(
|
|
110
|
+
rako_xml3, "Ventilation"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
expected_ventilation = [
|
|
114
|
+
RoomVentilation(room_id=161, room_title="Fans", channel_id=0),
|
|
115
|
+
ChannelVentilation(
|
|
116
|
+
room_id=161,
|
|
117
|
+
room_title="Fans",
|
|
118
|
+
channel_id=1,
|
|
119
|
+
channel_type="switch",
|
|
120
|
+
channel_name="Fans",
|
|
121
|
+
channel_levels="FFBF7F3F000000000000000000000000",
|
|
122
|
+
),
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
ventilation_list = [
|
|
126
|
+
dev
|
|
127
|
+
for dev in ventilation_devices
|
|
128
|
+
if isinstance(dev, (RoomVentilation, ChannelVentilation))
|
|
129
|
+
]
|
|
130
|
+
assert ventilation_list == expected_ventilation
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_ventilation_command_compatibility():
|
|
134
|
+
"""Test that ventilation can use the same commands as lighting"""
|
|
135
|
+
from python_rako.model import CommandLevelHTTP, CommandSceneHTTP
|
|
136
|
+
|
|
137
|
+
# Test that ventilation room can use lighting commands
|
|
138
|
+
room_scene_command = CommandSceneHTTP(room=161, channel=0, scene=1)
|
|
139
|
+
assert room_scene_command.as_params() == {"room": 161, "ch": 0, "sc": 1}
|
|
140
|
+
|
|
141
|
+
# Test that ventilation channel can use lighting level commands
|
|
142
|
+
channel_level_command = CommandLevelHTTP(room=161, channel=1, level=128)
|
|
143
|
+
assert channel_level_command.as_params() == {"room": 161, "ch": 1, "lev": 128}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_get_all_devices_from_discovery_xml(rako_xml3):
|
|
147
|
+
"""Test discovering all device types from XML"""
|
|
148
|
+
all_devices = list(Bridge.get_all_devices_from_discovery_xml(rako_xml3))
|
|
149
|
+
|
|
150
|
+
# Should contain both lights and ventilation
|
|
151
|
+
lights = [dev for dev in all_devices if isinstance(dev, (RoomLight, ChannelLight))]
|
|
152
|
+
ventilation = [
|
|
153
|
+
dev
|
|
154
|
+
for dev in all_devices
|
|
155
|
+
if isinstance(dev, (RoomVentilation, ChannelVentilation))
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
# Should have many lights and 2 ventilation devices
|
|
159
|
+
assert len(lights) > 10 # rako3.xml has many light rooms
|
|
160
|
+
assert len(ventilation) == 2 # 1 RoomVentilation + 1 ChannelVentilation
|
|
161
|
+
|
|
162
|
+
# Check specific ventilation devices are included
|
|
163
|
+
ventilation_room = next(
|
|
164
|
+
(dev for dev in ventilation if isinstance(dev, RoomVentilation)), None
|
|
165
|
+
)
|
|
166
|
+
assert ventilation_room is not None
|
|
167
|
+
assert ventilation_room.room_id == 161
|
|
168
|
+
assert ventilation_room.room_title == "Fans"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_get_devices_with_specific_types(rako_xml3):
|
|
172
|
+
"""Test discovering specific device types using list parameter"""
|
|
173
|
+
# Test with list of device types
|
|
174
|
+
lights_only = list(Bridge.get_devices_from_discovery_xml(rako_xml3, ["Lights"]))
|
|
175
|
+
ventilation_only = list(
|
|
176
|
+
Bridge.get_devices_from_discovery_xml(rako_xml3, ["Ventilation"])
|
|
177
|
+
)
|
|
178
|
+
both_types = list(
|
|
179
|
+
Bridge.get_devices_from_discovery_xml(rako_xml3, ["Lights", "Ventilation"])
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Verify filtering works correctly
|
|
183
|
+
assert all(isinstance(dev, (RoomLight, ChannelLight)) for dev in lights_only)
|
|
184
|
+
assert all(
|
|
185
|
+
isinstance(dev, (RoomVentilation, ChannelVentilation))
|
|
186
|
+
for dev in ventilation_only
|
|
187
|
+
)
|
|
188
|
+
assert len(both_types) == len(lights_only) + len(ventilation_only)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_get_devices_backward_compatibility(rako_xml3):
|
|
192
|
+
"""Test that existing string parameter still works"""
|
|
193
|
+
# Test backward compatibility with string parameter
|
|
194
|
+
lights_str = list(Bridge.get_devices_from_discovery_xml(rako_xml3, "Lights"))
|
|
195
|
+
lights_list = list(Bridge.get_devices_from_discovery_xml(rako_xml3, ["Lights"]))
|
|
196
|
+
|
|
197
|
+
# Should return the same devices
|
|
198
|
+
assert lights_str == lights_list
|
|
199
|
+
|
|
200
|
+
# Test ventilation backward compatibility
|
|
201
|
+
ventilation_str = list(
|
|
202
|
+
Bridge.get_devices_from_discovery_xml(rako_xml3, "Ventilation")
|
|
203
|
+
)
|
|
204
|
+
ventilation_list = list(
|
|
205
|
+
Bridge.get_devices_from_discovery_xml(rako_xml3, ["Ventilation"])
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
assert ventilation_str == ventilation_list
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_get_devices_all_parameter_variants(rako_xml3):
|
|
212
|
+
"""Test different ways to get all devices"""
|
|
213
|
+
all_devices_none = list(Bridge.get_devices_from_discovery_xml(rako_xml3, None))
|
|
214
|
+
all_devices_all = list(Bridge.get_devices_from_discovery_xml(rako_xml3, "All"))
|
|
215
|
+
all_devices_method = list(Bridge.get_all_devices_from_discovery_xml(rako_xml3))
|
|
216
|
+
|
|
217
|
+
# All should return the same devices
|
|
218
|
+
assert all_devices_none == all_devices_all == all_devices_method
|
|
219
|
+
|
|
220
|
+
# Should contain both lights and ventilation
|
|
221
|
+
assert any(isinstance(dev, (RoomLight, ChannelLight)) for dev in all_devices_none)
|
|
222
|
+
assert any(
|
|
223
|
+
isinstance(dev, (RoomVentilation, ChannelVentilation))
|
|
224
|
+
for dev in all_devices_none
|
|
225
|
+
)
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
from python_rako.bridge import Bridge
|
|
2
|
-
from python_rako.model import BridgeInfo, ChannelLight, RoomLight
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def test_get_lights_from_discovery_xml(rako_xml):
|
|
6
|
-
lights = Bridge.get_lights_from_discovery_xml(rako_xml)
|
|
7
|
-
|
|
8
|
-
expected_lights = [
|
|
9
|
-
RoomLight(room_id=5, room_title="Living Room", channel_id=0),
|
|
10
|
-
ChannelLight(
|
|
11
|
-
room_id=5,
|
|
12
|
-
room_title="Living Room",
|
|
13
|
-
channel_id=1,
|
|
14
|
-
channel_type="Default",
|
|
15
|
-
channel_name="Downlights",
|
|
16
|
-
channel_levels="FF347F3F000000000000000000000000",
|
|
17
|
-
),
|
|
18
|
-
ChannelLight(
|
|
19
|
-
room_id=5,
|
|
20
|
-
room_title="Living Room",
|
|
21
|
-
channel_id=2,
|
|
22
|
-
channel_type="Default",
|
|
23
|
-
channel_name="Kitchen Downlights",
|
|
24
|
-
channel_levels="FF337F3F000000000000000000000000",
|
|
25
|
-
),
|
|
26
|
-
RoomLight(room_id=9, room_title="Bedroom 1", channel_id=0),
|
|
27
|
-
ChannelLight(
|
|
28
|
-
room_id=9,
|
|
29
|
-
room_title="Bedroom 1",
|
|
30
|
-
channel_id=1,
|
|
31
|
-
channel_type="Default",
|
|
32
|
-
channel_name="Downlights",
|
|
33
|
-
channel_levels="FFBF7F25000000000000000000000000",
|
|
34
|
-
),
|
|
35
|
-
ChannelLight(
|
|
36
|
-
room_id=9,
|
|
37
|
-
room_title="Bedroom 1",
|
|
38
|
-
channel_id=2,
|
|
39
|
-
channel_type="Default",
|
|
40
|
-
channel_name="LED",
|
|
41
|
-
channel_levels="FFBF7F00000000000000000000000000",
|
|
42
|
-
),
|
|
43
|
-
]
|
|
44
|
-
assert list(lights) == expected_lights
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_get_bridge_info_from_discovery_xml(rako_xml):
|
|
48
|
-
info = Bridge.get_bridge_info_from_discovery_xml(rako_xml)
|
|
49
|
-
|
|
50
|
-
expected_info = BridgeInfo(
|
|
51
|
-
version="2.4.0 RA",
|
|
52
|
-
buildDate="Nov 17 2017 10:01:01",
|
|
53
|
-
hostName="RAKOBRIDGE",
|
|
54
|
-
hostIP="someip",
|
|
55
|
-
hostMAC="somemac",
|
|
56
|
-
hwStatus="05",
|
|
57
|
-
dbVersion="-31",
|
|
58
|
-
requirepassword=None,
|
|
59
|
-
passhash="NAN",
|
|
60
|
-
charset="UTF-8",
|
|
61
|
-
)
|
|
62
|
-
assert info == expected_info
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_get_bridge_info_from_discovery_xml2(rako_xml2):
|
|
66
|
-
info = Bridge.get_bridge_info_from_discovery_xml(rako_xml2)
|
|
67
|
-
|
|
68
|
-
expected_info = BridgeInfo(
|
|
69
|
-
version=None,
|
|
70
|
-
buildDate=None,
|
|
71
|
-
hostName=None,
|
|
72
|
-
hostIP=None,
|
|
73
|
-
hostMAC=None,
|
|
74
|
-
hwStatus=None,
|
|
75
|
-
dbVersion=None,
|
|
76
|
-
requirepassword=None,
|
|
77
|
-
passhash="NAN",
|
|
78
|
-
charset=None,
|
|
79
|
-
)
|
|
80
|
-
assert info == expected_info
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def test_get_lights_from_discovery_xml2(rako_xml2):
|
|
84
|
-
lights = Bridge.get_lights_from_discovery_xml(rako_xml2)
|
|
85
|
-
|
|
86
|
-
expected_lights = [
|
|
87
|
-
RoomLight(room_id=112, room_title="Bedroom 1", channel_id=0),
|
|
88
|
-
ChannelLight(
|
|
89
|
-
room_id=112,
|
|
90
|
-
room_title="Bedroom 1",
|
|
91
|
-
channel_id=1,
|
|
92
|
-
channel_type="Default",
|
|
93
|
-
channel_name="Ceiling Light",
|
|
94
|
-
channel_levels="FFBF7F3F000000000000000000000000",
|
|
95
|
-
),
|
|
96
|
-
RoomLight(room_id=108, room_title="Master Ensuite", channel_id=0),
|
|
97
|
-
]
|
|
98
|
-
|
|
99
|
-
assert list(lights) == expected_lights
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_rako_2025-0.0.1 → python_rako_2025-0.2.0}/python_rako_2025.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|