python-rako-2025 0.1.0__tar.gz → 0.3.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.1.0/python_rako_2025.egg-info → python_rako_2025-0.3.0}/PKG-INFO +1 -8
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/pyproject.toml +4 -1
- python_rako_2025-0.3.0/python_rako/__version__.py +1 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/bridge.py +49 -6
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0/python_rako_2025.egg-info}/PKG-INFO +1 -8
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako_2025.egg-info/SOURCES.txt +0 -1
- python_rako_2025-0.3.0/setup.py +3 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/tests/test_bridge.py +82 -0
- python_rako_2025-0.1.0/python_rako/__version__.py +0 -3
- python_rako_2025-0.1.0/python_rako_2025.egg-info/not-zip-safe +0 -1
- python_rako_2025-0.1.0/setup.py +0 -55
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/LICENSE +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/README.md +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/__init__.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/const.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/exceptions.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/helpers.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/model.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako/py.typed +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako_2025.egg-info/dependency_links.txt +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako_2025.egg-info/requires.txt +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/python_rako_2025.egg-info/top_level.txt +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/setup.cfg +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/tests/test_helpers.py +0 -0
- {python_rako_2025-0.1.0 → python_rako_2025-0.3.0}/tests/test_model.py +0 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-rako-2025
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Asynchronous Python client for Rako Controls Lighting
|
|
5
|
-
Home-page: https://github.com/simonleigh/python-rako
|
|
6
|
-
Author: Simon Leigh
|
|
7
5
|
Author-email: Simon Leigh <simonleigh@users.noreply.github.com>
|
|
8
6
|
Project-URL: Homepage, https://github.com/simonleigh/python-rako
|
|
9
7
|
Project-URL: Repository, https://github.com/simonleigh/python-rako
|
|
10
8
|
Project-URL: Issues, https://github.com/simonleigh/python-rako/issues
|
|
11
9
|
Keywords: rako,controls,api,async,client
|
|
12
|
-
Platform: any
|
|
13
10
|
Classifier: Development Status :: 4 - Beta
|
|
14
11
|
Classifier: Framework :: AsyncIO
|
|
15
12
|
Classifier: Intended Audience :: Developers
|
|
@@ -28,11 +25,7 @@ License-File: LICENSE
|
|
|
28
25
|
Requires-Dist: aiohttp>=3.10.0
|
|
29
26
|
Requires-Dist: asyncio-dgram>=2.2.0
|
|
30
27
|
Requires-Dist: xmltodict>=0.13.0
|
|
31
|
-
Dynamic: author
|
|
32
|
-
Dynamic: home-page
|
|
33
28
|
Dynamic: license-file
|
|
34
|
-
Dynamic: platform
|
|
35
|
-
Dynamic: requires-python
|
|
36
29
|
|
|
37
30
|
# Python: Rako Controls API Client
|
|
38
31
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-rako-2025"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "Asynchronous Python client for Rako Controls Lighting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = ["rako", "controls", "api", "async", "client"]
|
|
@@ -44,6 +44,9 @@ include = ["python_rako*"]
|
|
|
44
44
|
[tool.setuptools.package-data]
|
|
45
45
|
python_rako = ["py.typed"]
|
|
46
46
|
|
|
47
|
+
[tool.setuptools.dynamic]
|
|
48
|
+
version = {file = "python_rako/__version__.py"}
|
|
49
|
+
|
|
47
50
|
[tool.ruff]
|
|
48
51
|
target-version = "py310"
|
|
49
52
|
line-length = 100
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.0
|
|
@@ -160,9 +160,30 @@ class Bridge:
|
|
|
160
160
|
rako_xml: str = await response.text()
|
|
161
161
|
return rako_xml
|
|
162
162
|
|
|
163
|
+
async def discover_devices(
|
|
164
|
+
self, session: aiohttp.ClientSession
|
|
165
|
+
) -> tuple[list[Light], list[Ventilation]]:
|
|
166
|
+
"""
|
|
167
|
+
Discover all devices by fetching XML once and parsing all device types.
|
|
168
|
+
Returns a tuple of (lights, ventilation) to avoid race conditions.
|
|
169
|
+
"""
|
|
170
|
+
rako_xml = await self.get_rako_xml(session)
|
|
171
|
+
|
|
172
|
+
lights = []
|
|
173
|
+
ventilation = []
|
|
174
|
+
|
|
175
|
+
for device in self.get_devices_from_discovery_xml(rako_xml):
|
|
176
|
+
if isinstance(device, (RoomLight, ChannelLight)):
|
|
177
|
+
lights.append(device)
|
|
178
|
+
elif isinstance(device, (RoomVentilation, ChannelVentilation)):
|
|
179
|
+
ventilation.append(device)
|
|
180
|
+
|
|
181
|
+
return lights, ventilation
|
|
182
|
+
|
|
163
183
|
async def discover_lights(
|
|
164
184
|
self, session: aiohttp.ClientSession
|
|
165
185
|
) -> AsyncGenerator[Light]:
|
|
186
|
+
"""Discover lights by fetching XML once and filtering for lights."""
|
|
166
187
|
rako_xml = await self.get_rako_xml(session)
|
|
167
188
|
for light in self.get_lights_from_discovery_xml(rako_xml):
|
|
168
189
|
yield light
|
|
@@ -170,11 +191,20 @@ class Bridge:
|
|
|
170
191
|
async def discover_ventilation(
|
|
171
192
|
self, session: aiohttp.ClientSession
|
|
172
193
|
) -> AsyncGenerator[Ventilation]:
|
|
194
|
+
"""Discover ventilation by fetching XML once and filtering for ventilation."""
|
|
173
195
|
rako_xml = await self.get_rako_xml(session)
|
|
174
196
|
for device in self.get_devices_from_discovery_xml(rako_xml, "Ventilation"):
|
|
175
197
|
if isinstance(device, (RoomVentilation, ChannelVentilation)):
|
|
176
198
|
yield device
|
|
177
199
|
|
|
200
|
+
async def discover_all_devices(
|
|
201
|
+
self, session: aiohttp.ClientSession
|
|
202
|
+
) -> AsyncGenerator[Light | Ventilation]:
|
|
203
|
+
"""Discover all devices by fetching XML once."""
|
|
204
|
+
rako_xml = await self.get_rako_xml(session)
|
|
205
|
+
for device in self.get_devices_from_discovery_xml(rako_xml):
|
|
206
|
+
yield device
|
|
207
|
+
|
|
178
208
|
async def get_info(self, session: aiohttp.ClientSession) -> BridgeInfo:
|
|
179
209
|
try:
|
|
180
210
|
rako_xml = await self.get_rako_xml(session)
|
|
@@ -209,22 +239,35 @@ class Bridge:
|
|
|
209
239
|
if isinstance(device, (RoomLight, ChannelLight)):
|
|
210
240
|
yield device
|
|
211
241
|
|
|
242
|
+
@staticmethod
|
|
243
|
+
def get_all_devices_from_discovery_xml(xml: str) -> Generator[Light | Ventilation]:
|
|
244
|
+
"""Get all devices (lights and ventilation) from discovery XML."""
|
|
245
|
+
return Bridge.get_devices_from_discovery_xml(xml)
|
|
246
|
+
|
|
212
247
|
@staticmethod
|
|
213
248
|
def get_devices_from_discovery_xml(
|
|
214
|
-
xml: str,
|
|
249
|
+
xml: str, device_types: str | list[str] | None = None
|
|
215
250
|
) -> Generator[Light | Ventilation]:
|
|
251
|
+
# Handle different input types for backward compatibility
|
|
252
|
+
if device_types is None or device_types == "All":
|
|
253
|
+
target_types = {"Lights", "Ventilation"}
|
|
254
|
+
elif isinstance(device_types, str):
|
|
255
|
+
target_types = {device_types}
|
|
256
|
+
else:
|
|
257
|
+
target_types = set(device_types)
|
|
258
|
+
|
|
216
259
|
xml_dict = xmltodict.parse(xml, force_list={"Room"})
|
|
217
260
|
for room in xml_dict["rako"]["rooms"]["Room"]:
|
|
218
261
|
room_id = int(room["@id"])
|
|
219
262
|
room_type = room.get("Type", "Lights")
|
|
220
|
-
if room_type
|
|
263
|
+
if room_type not in target_types:
|
|
221
264
|
continue
|
|
222
265
|
room_title = room["Title"]
|
|
223
266
|
|
|
224
267
|
# Yield room-level device
|
|
225
|
-
if
|
|
268
|
+
if room_type == "Lights":
|
|
226
269
|
yield RoomLight(room_id, room_title)
|
|
227
|
-
elif
|
|
270
|
+
elif room_type == "Ventilation":
|
|
228
271
|
yield RoomVentilation(room_id, room_title)
|
|
229
272
|
|
|
230
273
|
# Yield channel-level devices
|
|
@@ -240,7 +283,7 @@ class Bridge:
|
|
|
240
283
|
channel_name = channel["Name"]
|
|
241
284
|
channel_levels = channel["Levels"]
|
|
242
285
|
|
|
243
|
-
if
|
|
286
|
+
if room_type == "Lights":
|
|
244
287
|
yield ChannelLight(
|
|
245
288
|
room_id,
|
|
246
289
|
room_title,
|
|
@@ -249,7 +292,7 @@ class Bridge:
|
|
|
249
292
|
channel_name,
|
|
250
293
|
channel_levels,
|
|
251
294
|
)
|
|
252
|
-
elif
|
|
295
|
+
elif room_type == "Ventilation":
|
|
253
296
|
yield ChannelVentilation(
|
|
254
297
|
room_id,
|
|
255
298
|
room_title,
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-rako-2025
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Asynchronous Python client for Rako Controls Lighting
|
|
5
|
-
Home-page: https://github.com/simonleigh/python-rako
|
|
6
|
-
Author: Simon Leigh
|
|
7
5
|
Author-email: Simon Leigh <simonleigh@users.noreply.github.com>
|
|
8
6
|
Project-URL: Homepage, https://github.com/simonleigh/python-rako
|
|
9
7
|
Project-URL: Repository, https://github.com/simonleigh/python-rako
|
|
10
8
|
Project-URL: Issues, https://github.com/simonleigh/python-rako/issues
|
|
11
9
|
Keywords: rako,controls,api,async,client
|
|
12
|
-
Platform: any
|
|
13
10
|
Classifier: Development Status :: 4 - Beta
|
|
14
11
|
Classifier: Framework :: AsyncIO
|
|
15
12
|
Classifier: Intended Audience :: Developers
|
|
@@ -28,11 +25,7 @@ License-File: LICENSE
|
|
|
28
25
|
Requires-Dist: aiohttp>=3.10.0
|
|
29
26
|
Requires-Dist: asyncio-dgram>=2.2.0
|
|
30
27
|
Requires-Dist: xmltodict>=0.13.0
|
|
31
|
-
Dynamic: author
|
|
32
|
-
Dynamic: home-page
|
|
33
28
|
Dynamic: license-file
|
|
34
|
-
Dynamic: platform
|
|
35
|
-
Dynamic: requires-python
|
|
36
29
|
|
|
37
30
|
# Python: Rako Controls API Client
|
|
38
31
|
|
|
@@ -13,7 +13,6 @@ python_rako/py.typed
|
|
|
13
13
|
python_rako_2025.egg-info/PKG-INFO
|
|
14
14
|
python_rako_2025.egg-info/SOURCES.txt
|
|
15
15
|
python_rako_2025.egg-info/dependency_links.txt
|
|
16
|
-
python_rako_2025.egg-info/not-zip-safe
|
|
17
16
|
python_rako_2025.egg-info/requires.txt
|
|
18
17
|
python_rako_2025.egg-info/top_level.txt
|
|
19
18
|
tests/test_bridge.py
|
|
@@ -141,3 +141,85 @@ def test_ventilation_command_compatibility():
|
|
|
141
141
|
# Test that ventilation channel can use lighting level commands
|
|
142
142
|
channel_level_command = CommandLevelHTTP(room=161, channel=1, level=128)
|
|
143
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 +0,0 @@
|
|
|
1
|
-
|
python_rako_2025-0.1.0/setup.py
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from setuptools import find_packages, setup
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def read(*parts):
|
|
7
|
-
"""Read file."""
|
|
8
|
-
filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)
|
|
9
|
-
with open(filename, encoding="utf-8") as fp:
|
|
10
|
-
return fp.read()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
with open("README.md") as readme_file:
|
|
14
|
-
readme = readme_file.read()
|
|
15
|
-
|
|
16
|
-
setup(
|
|
17
|
-
name="python-rako-2025",
|
|
18
|
-
version="0.1.0",
|
|
19
|
-
license="MIT",
|
|
20
|
-
url="https://github.com/simonleigh/python-rako",
|
|
21
|
-
author="Simon Leigh",
|
|
22
|
-
author_email="simonleigh@users.noreply.github.com",
|
|
23
|
-
description="Asynchronous Python client for Rako Controls Lighting.",
|
|
24
|
-
keywords=["rako", "controls", "api", "async", "client"],
|
|
25
|
-
long_description=readme,
|
|
26
|
-
long_description_content_type="text/markdown",
|
|
27
|
-
packages=find_packages(include=["python_rako"]),
|
|
28
|
-
zip_safe=False,
|
|
29
|
-
include_package_data=True,
|
|
30
|
-
platforms="any",
|
|
31
|
-
package_data={
|
|
32
|
-
"python_rako": ["py.typed"],
|
|
33
|
-
},
|
|
34
|
-
install_requires=[
|
|
35
|
-
"aiohttp>=3.10.0",
|
|
36
|
-
"asyncio-dgram>=2.2.0",
|
|
37
|
-
"xmltodict>=0.13.0",
|
|
38
|
-
],
|
|
39
|
-
classifiers=[
|
|
40
|
-
"Intended Audience :: Developers",
|
|
41
|
-
"License :: OSI Approved :: MIT License",
|
|
42
|
-
"Operating System :: OS Independent",
|
|
43
|
-
"Programming Language :: Python",
|
|
44
|
-
"Programming Language :: Python :: 3",
|
|
45
|
-
"Programming Language :: Python :: 3.10",
|
|
46
|
-
"Programming Language :: Python :: 3.11",
|
|
47
|
-
"Programming Language :: Python :: 3.12",
|
|
48
|
-
"Programming Language :: Python :: 3.13",
|
|
49
|
-
"Natural Language :: English",
|
|
50
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
51
|
-
"Framework :: AsyncIO",
|
|
52
|
-
"Development Status :: 4 - Beta",
|
|
53
|
-
],
|
|
54
|
-
python_requires=">=3.10",
|
|
55
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_rako_2025-0.1.0 → python_rako_2025-0.3.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
|