violet-poolController-api 0.0.9__tar.gz → 0.0.10__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.
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/PKG-INFO +17 -1
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/README.md +16 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/pyproject.toml +1 -1
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/setup.py +1 -1
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/tests/test_api.py +61 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolController_api.egg-info/PKG-INFO +17 -1
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/api.py +76 -1
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/LICENSE +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/setup.cfg +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolController_api.egg-info/SOURCES.txt +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolController_api.egg-info/requires.txt +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolController_api.egg-info/top_level.txt +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/__init__.py +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/circuit_breaker.py +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/const_api.py +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/const_devices.py +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/utils_rate_limiter.py +0 -0
- {violet_poolcontroller_api-0.0.9 → violet_poolcontroller_api-0.0.10}/violet_poolcontroller_api/utils_sanitizer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: violet-poolController-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: Asynchronous Python client for the Violet Pool Controller.
|
|
5
5
|
Home-page: https://github.com/Xerolux/violet-poolController-api
|
|
6
6
|
Author: Basti (Xerolux)
|
|
@@ -136,6 +136,22 @@ In this mode, dosing functions (for example `manual_dosing` and dosing parameter
|
|
|
136
136
|
**Note on getReadings format:**
|
|
137
137
|
As of version `0.0.7`, the API client automatically detects and normalizes the payload output from the controller. Whether your Violet Controller returns the classic base-module `dict` structure (`{"PUMPSTATE": "2", "PH": 7.2}`) or the new standalone `list` structure, the `get_readings()` and `get_specific_readings()` functions will always return a seamless, flattened key-value dictionary. Your Home Assistant integration or downstream application will work uniformly with both formats without requiring any extra code!
|
|
138
138
|
|
|
139
|
+
**Hardware Profile Detection:**
|
|
140
|
+
As of the latest release, the API client provides a method to detect the specific hardware configuration of your Violet Controller.
|
|
141
|
+
The API automatically detects the connected modules and updates internal states based on the available readings.
|
|
142
|
+
```python
|
|
143
|
+
profile = await api.get_hardware_profile()
|
|
144
|
+
print(profile)
|
|
145
|
+
# Output example:
|
|
146
|
+
# {
|
|
147
|
+
# "base_module": True,
|
|
148
|
+
# "dosing_module": True,
|
|
149
|
+
# "extension_module_1": True,
|
|
150
|
+
# "extension_module_2": False,
|
|
151
|
+
# }
|
|
152
|
+
```
|
|
153
|
+
This detection parses `get_readings()` to check for the presence of certain internal status parameters (`SYSTEM_dosagemodule_cpu_temperature`, `EXT1_1`, `EXT2_1`), allowing your application to dynamically adapt to the connected modules (Base Module, Dosing Module, Relay Extension 1 and 2). By utilizing this detection, developers and integrations can accurately filter out features for missing hardware, ensuring that only supported options are exposed to the user.
|
|
154
|
+
|
|
139
155
|
## License
|
|
140
156
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
141
157
|
|
|
@@ -108,6 +108,22 @@ In this mode, dosing functions (for example `manual_dosing` and dosing parameter
|
|
|
108
108
|
**Note on getReadings format:**
|
|
109
109
|
As of version `0.0.7`, the API client automatically detects and normalizes the payload output from the controller. Whether your Violet Controller returns the classic base-module `dict` structure (`{"PUMPSTATE": "2", "PH": 7.2}`) or the new standalone `list` structure, the `get_readings()` and `get_specific_readings()` functions will always return a seamless, flattened key-value dictionary. Your Home Assistant integration or downstream application will work uniformly with both formats without requiring any extra code!
|
|
110
110
|
|
|
111
|
+
**Hardware Profile Detection:**
|
|
112
|
+
As of the latest release, the API client provides a method to detect the specific hardware configuration of your Violet Controller.
|
|
113
|
+
The API automatically detects the connected modules and updates internal states based on the available readings.
|
|
114
|
+
```python
|
|
115
|
+
profile = await api.get_hardware_profile()
|
|
116
|
+
print(profile)
|
|
117
|
+
# Output example:
|
|
118
|
+
# {
|
|
119
|
+
# "base_module": True,
|
|
120
|
+
# "dosing_module": True,
|
|
121
|
+
# "extension_module_1": True,
|
|
122
|
+
# "extension_module_2": False,
|
|
123
|
+
# }
|
|
124
|
+
```
|
|
125
|
+
This detection parses `get_readings()` to check for the presence of certain internal status parameters (`SYSTEM_dosagemodule_cpu_temperature`, `EXT1_1`, `EXT2_1`), allowing your application to dynamically adapt to the connected modules (Base Module, Dosing Module, Relay Extension 1 and 2). By utilizing this detection, developers and integrations can accurately filter out features for missing hardware, ensuring that only supported options are exposed to the user.
|
|
126
|
+
|
|
111
127
|
## License
|
|
112
128
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
113
129
|
|
|
@@ -254,3 +254,64 @@ async def test_dosing_standalone_detection_dict_format(mock_aioresponse, standal
|
|
|
254
254
|
|
|
255
255
|
assert isinstance(result, dict)
|
|
256
256
|
assert standalone_api_client.dosing_standalone is False
|
|
257
|
+
|
|
258
|
+
@pytest.mark.asyncio
|
|
259
|
+
async def test_get_hardware_profile(mock_aioresponse, api_client):
|
|
260
|
+
"""Test get_hardware_profile correctly detects components."""
|
|
261
|
+
url = "http://192.168.1.100/getReadings?ALL"
|
|
262
|
+
|
|
263
|
+
# 1. Base module only (no DOS, EXT)
|
|
264
|
+
mock_aioresponse.get(url, payload={"getReadings": {"PUMPSTATE": "2", "SYSTEM_dosagemodule_cpu_temperature": "N/A"}}, status=200)
|
|
265
|
+
profile = await api_client.get_hardware_profile()
|
|
266
|
+
assert profile == {
|
|
267
|
+
"base_module": True,
|
|
268
|
+
"dosing_module": False,
|
|
269
|
+
"extension_module_1": False,
|
|
270
|
+
"extension_module_2": False,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# 2. Base module + Dosing + Ext1
|
|
274
|
+
mock_aioresponse.get(url, payload={"getReadings": {"PUMPSTATE": "2", "SYSTEM_dosagemodule_cpu_temperature": 45.5, "EXT1_1": "1"}}, status=200)
|
|
275
|
+
profile = await api_client.get_hardware_profile()
|
|
276
|
+
assert profile == {
|
|
277
|
+
"base_module": True,
|
|
278
|
+
"dosing_module": True,
|
|
279
|
+
"extension_module_1": True,
|
|
280
|
+
"extension_module_2": False,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# 3. Base module + Ext1 + Ext2 (no Dosing)
|
|
284
|
+
mock_aioresponse.get(url, payload={"getReadings": {"PUMPSTATE": "2", "EXT1_1": "1", "EXT2_1": "1"}}, status=200)
|
|
285
|
+
profile = await api_client.get_hardware_profile()
|
|
286
|
+
assert profile == {
|
|
287
|
+
"base_module": True,
|
|
288
|
+
"dosing_module": False,
|
|
289
|
+
"extension_module_1": True,
|
|
290
|
+
"extension_module_2": True,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@pytest.mark.asyncio
|
|
294
|
+
async def test_get_hardware_profile_standalone_dosing(mock_aioresponse, standalone_api_client):
|
|
295
|
+
"""Test get_hardware_profile with a standalone dosing configuration."""
|
|
296
|
+
url = "http://192.168.1.100/getReadings?ALL"
|
|
297
|
+
# Using the standalone list format
|
|
298
|
+
mock_data = {
|
|
299
|
+
"getReadings": [
|
|
300
|
+
{
|
|
301
|
+
"VALUE NAME": " \"DOS_1_CL\"",
|
|
302
|
+
"DESCRIPTION": "Current state of OUTPUT: CL-DOSING",
|
|
303
|
+
"FORMAT": "INTEGER",
|
|
304
|
+
"DETAILS": "0 - AUTO (not on)",
|
|
305
|
+
"VALUE": 2
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
mock_aioresponse.get(url, payload=mock_data, status=200)
|
|
310
|
+
|
|
311
|
+
profile = await standalone_api_client.get_hardware_profile()
|
|
312
|
+
assert profile == {
|
|
313
|
+
"base_module": False,
|
|
314
|
+
"dosing_module": True,
|
|
315
|
+
"extension_module_1": False,
|
|
316
|
+
"extension_module_2": False,
|
|
317
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: violet-poolController-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: Asynchronous Python client for the Violet Pool Controller.
|
|
5
5
|
Home-page: https://github.com/Xerolux/violet-poolController-api
|
|
6
6
|
Author: Basti (Xerolux)
|
|
@@ -136,6 +136,22 @@ In this mode, dosing functions (for example `manual_dosing` and dosing parameter
|
|
|
136
136
|
**Note on getReadings format:**
|
|
137
137
|
As of version `0.0.7`, the API client automatically detects and normalizes the payload output from the controller. Whether your Violet Controller returns the classic base-module `dict` structure (`{"PUMPSTATE": "2", "PH": 7.2}`) or the new standalone `list` structure, the `get_readings()` and `get_specific_readings()` functions will always return a seamless, flattened key-value dictionary. Your Home Assistant integration or downstream application will work uniformly with both formats without requiring any extra code!
|
|
138
138
|
|
|
139
|
+
**Hardware Profile Detection:**
|
|
140
|
+
As of the latest release, the API client provides a method to detect the specific hardware configuration of your Violet Controller.
|
|
141
|
+
The API automatically detects the connected modules and updates internal states based on the available readings.
|
|
142
|
+
```python
|
|
143
|
+
profile = await api.get_hardware_profile()
|
|
144
|
+
print(profile)
|
|
145
|
+
# Output example:
|
|
146
|
+
# {
|
|
147
|
+
# "base_module": True,
|
|
148
|
+
# "dosing_module": True,
|
|
149
|
+
# "extension_module_1": True,
|
|
150
|
+
# "extension_module_2": False,
|
|
151
|
+
# }
|
|
152
|
+
```
|
|
153
|
+
This detection parses `get_readings()` to check for the presence of certain internal status parameters (`SYSTEM_dosagemodule_cpu_temperature`, `EXT1_1`, `EXT2_1`), allowing your application to dynamically adapt to the connected modules (Base Module, Dosing Module, Relay Extension 1 and 2). By utilizing this detection, developers and integrations can accurately filter out features for missing hardware, ensuring that only supported options are exposed to the user.
|
|
154
|
+
|
|
139
155
|
## License
|
|
140
156
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
141
157
|
|
|
@@ -511,7 +511,57 @@ class VioletPoolAPI:
|
|
|
511
511
|
query="ALL",
|
|
512
512
|
payload_name="getReadings",
|
|
513
513
|
)
|
|
514
|
-
|
|
514
|
+
flattened = self._flatten_getreadings_response(response)
|
|
515
|
+
|
|
516
|
+
# Determine hardware profile based on the flattened readings
|
|
517
|
+
def is_present(key: str) -> bool:
|
|
518
|
+
if not isinstance(flattened, dict):
|
|
519
|
+
return False
|
|
520
|
+
val = flattened.get(key)
|
|
521
|
+
return val is not None and str(val).strip().upper() != "N/A"
|
|
522
|
+
|
|
523
|
+
profile = {
|
|
524
|
+
"base_module": not self.dosing_standalone,
|
|
525
|
+
"dosing_module": self.dosing_standalone or is_present("SYSTEM_dosagemodule_cpu_temperature"),
|
|
526
|
+
"extension_module_1": is_present("EXT1_1"),
|
|
527
|
+
"extension_module_2": is_present("EXT2_1"),
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return self._filter_unsupported_readings(flattened, profile)
|
|
531
|
+
|
|
532
|
+
def _filter_unsupported_readings(self, readings: dict[str, Any], profile: dict[str, bool]) -> dict[str, Any]:
|
|
533
|
+
"""Filters out readings for hardware modules that are not present."""
|
|
534
|
+
if not isinstance(readings, dict):
|
|
535
|
+
return readings
|
|
536
|
+
|
|
537
|
+
filtered_readings = {}
|
|
538
|
+
for key, value in readings.items():
|
|
539
|
+
normalized_key = key.upper()
|
|
540
|
+
|
|
541
|
+
# Base Module specific keys
|
|
542
|
+
if not profile.get("base_module"):
|
|
543
|
+
if normalized_key in {
|
|
544
|
+
"PUMPSTATE", "PUMP_SPEED", "HEATER", "LIGHT", "ECO",
|
|
545
|
+
"BACKWASH", "BACKWASHRINSE", "REFILL", "PVSURPLUS", "TEMP_PUMP"
|
|
546
|
+
} or (normalized_key.startswith("SYSTEM_") and normalized_key != "SYSTEM_DOSAGEMODULE_CPU_TEMPERATURE"):
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
# Extension Module 1 specific keys
|
|
550
|
+
if not profile.get("extension_module_1") and normalized_key.startswith("EXT1_"):
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
# Extension Module 2 specific keys
|
|
554
|
+
if not profile.get("extension_module_2") and normalized_key.startswith("EXT2_"):
|
|
555
|
+
continue
|
|
556
|
+
|
|
557
|
+
# Dosing Module specific keys
|
|
558
|
+
if not profile.get("dosing_module"):
|
|
559
|
+
if normalized_key.startswith("DOS_") or normalized_key == "SYSTEM_DOSAGEMODULE_CPU_TEMPERATURE":
|
|
560
|
+
continue
|
|
561
|
+
|
|
562
|
+
filtered_readings[key] = value
|
|
563
|
+
|
|
564
|
+
return filtered_readings
|
|
515
565
|
|
|
516
566
|
async def get_specific_readings(
|
|
517
567
|
self, categories: list[str] | tuple[str, ...]
|
|
@@ -575,6 +625,31 @@ class VioletPoolAPI:
|
|
|
575
625
|
payload_name="getWeatherdata",
|
|
576
626
|
)
|
|
577
627
|
|
|
628
|
+
async def get_hardware_profile(self) -> dict[str, bool]:
|
|
629
|
+
"""Detects connected hardware based on available readings.
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
A dictionary with boolean flags for connected hardware components:
|
|
633
|
+
- base_module: True if the base module is present.
|
|
634
|
+
- dosing_module: True if the dosing module is present.
|
|
635
|
+
- extension_module_1: True if the first relay extension is present.
|
|
636
|
+
- extension_module_2: True if the second relay extension is present.
|
|
637
|
+
"""
|
|
638
|
+
readings = await self.get_readings()
|
|
639
|
+
|
|
640
|
+
def is_present(key: str) -> bool:
|
|
641
|
+
if not isinstance(readings, dict):
|
|
642
|
+
return False
|
|
643
|
+
val = readings.get(key)
|
|
644
|
+
return val is not None and str(val).strip().upper() != "N/A"
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
"base_module": not self.dosing_standalone,
|
|
648
|
+
"dosing_module": self.dosing_standalone or is_present("SYSTEM_dosagemodule_cpu_temperature"),
|
|
649
|
+
"extension_module_1": is_present("EXT1_1"),
|
|
650
|
+
"extension_module_2": is_present("EXT2_1"),
|
|
651
|
+
}
|
|
652
|
+
|
|
578
653
|
async def get_overall_dosing(self) -> dict[str, Any]:
|
|
579
654
|
"""Returns aggregated dosing statistics.
|
|
580
655
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|