violet-poolController-api 0.0.5__tar.gz → 0.0.6__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.5 → violet_poolcontroller_api-0.0.6}/PKG-INFO +19 -2
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/README.md +23 -6
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/pyproject.toml +1 -1
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/setup.py +1 -1
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/tests/test_api.py +42 -5
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolController_api.egg-info/PKG-INFO +19 -2
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/api.py +83 -43
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/circuit_breaker.py +2 -1
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/const_devices.py +1 -1
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/LICENSE +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/setup.cfg +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolController_api.egg-info/SOURCES.txt +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolController_api.egg-info/requires.txt +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolController_api.egg-info/top_level.txt +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/__init__.py +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/const_api.py +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/utils_rate_limiter.py +0 -0
- {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/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.6
|
|
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)
|
|
@@ -74,7 +74,8 @@ async def main():
|
|
|
74
74
|
host="192.168.1.100",
|
|
75
75
|
username="admin",
|
|
76
76
|
password="your_password",
|
|
77
|
-
session=session
|
|
77
|
+
session=session,
|
|
78
|
+
dosing_standalone=False, # True for Violet dosing standalone setups
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
try:
|
|
@@ -116,6 +117,22 @@ The API client includes many more functions tailored to the Violet Controller:
|
|
|
116
117
|
|
|
117
118
|
For a full list of available commands and more detailed examples, please refer to the [Wiki](https://github.com/Xerolux/violet-poolController-api/wiki) or the source code in `api.py`.
|
|
118
119
|
|
|
120
|
+
## Violet Dosing Standalone Mode
|
|
121
|
+
|
|
122
|
+
If your Violet setup runs as dosing standalone (without the base module), enable:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
api = VioletPoolAPI(
|
|
126
|
+
host="192.168.1.100",
|
|
127
|
+
username="admin",
|
|
128
|
+
password="your_password",
|
|
129
|
+
session=session,
|
|
130
|
+
dosing_standalone=True,
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
In this mode, dosing functions (for example `manual_dosing` and dosing parameter/target updates) stay available, while base-module-only switch functions (for example pump/light/backwash) are blocked with a clear error message.
|
|
135
|
+
|
|
119
136
|
## License
|
|
120
137
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
121
138
|
|
|
@@ -12,11 +12,11 @@ An asynchronous Python client for interacting with the **Violet Pool Controller*
|
|
|
12
12
|
|
|
13
13
|
This library is primarily designed to power the official [Violet Pool Controller Home Assistant Integration](https://github.com/Xerolux/violet-hass), but it can be used independently for any Python project that needs to fetch readings or control a Violet Pool system.
|
|
14
14
|
|
|
15
|
-
> **📖 Documentation:**
|
|
16
|
-
> - GitHub Pages: https://xerolux.github.io/violet-poolController-api/
|
|
17
|
-
> - GitHub Wiki: https://github.com/Xerolux/violet-poolController-api/wiki
|
|
18
|
-
>
|
|
19
|
-
> The `docs/` directory is the single source of truth and is used for both GitHub Pages and Wiki sync.
|
|
15
|
+
> **📖 Documentation:**
|
|
16
|
+
> - GitHub Pages: https://xerolux.github.io/violet-poolController-api/
|
|
17
|
+
> - GitHub Wiki: https://github.com/Xerolux/violet-poolController-api/wiki
|
|
18
|
+
>
|
|
19
|
+
> The `docs/` directory is the single source of truth and is used for both GitHub Pages and Wiki sync.
|
|
20
20
|
|
|
21
21
|
## Features
|
|
22
22
|
* **Asynchronous:** Fully async operations using `aiohttp`.
|
|
@@ -46,7 +46,8 @@ async def main():
|
|
|
46
46
|
host="192.168.1.100",
|
|
47
47
|
username="admin",
|
|
48
48
|
password="your_password",
|
|
49
|
-
session=session
|
|
49
|
+
session=session,
|
|
50
|
+
dosing_standalone=False, # True for Violet dosing standalone setups
|
|
50
51
|
)
|
|
51
52
|
|
|
52
53
|
try:
|
|
@@ -88,6 +89,22 @@ The API client includes many more functions tailored to the Violet Controller:
|
|
|
88
89
|
|
|
89
90
|
For a full list of available commands and more detailed examples, please refer to the [Wiki](https://github.com/Xerolux/violet-poolController-api/wiki) or the source code in `api.py`.
|
|
90
91
|
|
|
92
|
+
## Violet Dosing Standalone Mode
|
|
93
|
+
|
|
94
|
+
If your Violet setup runs as dosing standalone (without the base module), enable:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
api = VioletPoolAPI(
|
|
98
|
+
host="192.168.1.100",
|
|
99
|
+
username="admin",
|
|
100
|
+
password="your_password",
|
|
101
|
+
session=session,
|
|
102
|
+
dosing_standalone=True,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
In this mode, dosing functions (for example `manual_dosing` and dosing parameter/target updates) stay available, while base-module-only switch functions (for example pump/light/backwash) are blocked with a clear error message.
|
|
107
|
+
|
|
91
108
|
## License
|
|
92
109
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
93
110
|
|
|
@@ -26,8 +26,8 @@ def mock_aioresponse():
|
|
|
26
26
|
with aioresponses() as m:
|
|
27
27
|
yield m
|
|
28
28
|
|
|
29
|
-
@pytest_asyncio.fixture
|
|
30
|
-
async def api_client():
|
|
29
|
+
@pytest_asyncio.fixture
|
|
30
|
+
async def api_client():
|
|
31
31
|
async with aiohttp.ClientSession() as session:
|
|
32
32
|
# Pass low retry counts to make error tests faster
|
|
33
33
|
api = VioletPoolAPI(
|
|
@@ -36,9 +36,23 @@ async def api_client():
|
|
|
36
36
|
username="admin",
|
|
37
37
|
password="password",
|
|
38
38
|
max_retries=1
|
|
39
|
-
)
|
|
40
|
-
yield api
|
|
41
|
-
|
|
39
|
+
)
|
|
40
|
+
yield api
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest_asyncio.fixture
|
|
44
|
+
async def standalone_api_client():
|
|
45
|
+
async with aiohttp.ClientSession() as session:
|
|
46
|
+
api = VioletPoolAPI(
|
|
47
|
+
host="192.168.1.100",
|
|
48
|
+
session=session,
|
|
49
|
+
username="admin",
|
|
50
|
+
password="password",
|
|
51
|
+
max_retries=1,
|
|
52
|
+
dosing_standalone=True,
|
|
53
|
+
)
|
|
54
|
+
yield api
|
|
55
|
+
|
|
42
56
|
@pytest.mark.asyncio
|
|
43
57
|
async def test_get_readings_success(mock_aioresponse, api_client):
|
|
44
58
|
"""Test get_readings returns the correct parsed JSON dictionary."""
|
|
@@ -165,3 +179,26 @@ async def test_set_config_sanitizes_payload_before_request(api_client, monkeypat
|
|
|
165
179
|
assert result["success"] is True
|
|
166
180
|
assert result["response"] == "OK"
|
|
167
181
|
assert captured["json_payload"] == {"poolmode": "A<mode>", "speed": 3.7}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_standalone_mode_allows_manual_dosing(
|
|
186
|
+
mock_aioresponse, standalone_api_client
|
|
187
|
+
):
|
|
188
|
+
"""Standalone mode must still allow dosing outputs."""
|
|
189
|
+
url = "http://192.168.1.100/setFunctionManually?DOS_1_CL,ON,45,0"
|
|
190
|
+
mock_aioresponse.get(url, body="OK", status=200)
|
|
191
|
+
|
|
192
|
+
result = await standalone_api_client.manual_dosing("Chlor", 45)
|
|
193
|
+
|
|
194
|
+
assert result["success"] is True
|
|
195
|
+
assert result["response"] == "OK"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@pytest.mark.asyncio
|
|
199
|
+
async def test_standalone_mode_blocks_base_module_functions(standalone_api_client):
|
|
200
|
+
"""Standalone mode must reject functions that require the base module."""
|
|
201
|
+
with pytest.raises(VioletPoolAPIError) as exc_info:
|
|
202
|
+
await standalone_api_client.set_pump_speed(speed=2, duration=0)
|
|
203
|
+
|
|
204
|
+
assert "requires the Violet base module" in str(exc_info.value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: violet-poolController-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
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)
|
|
@@ -74,7 +74,8 @@ async def main():
|
|
|
74
74
|
host="192.168.1.100",
|
|
75
75
|
username="admin",
|
|
76
76
|
password="your_password",
|
|
77
|
-
session=session
|
|
77
|
+
session=session,
|
|
78
|
+
dosing_standalone=False, # True for Violet dosing standalone setups
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
try:
|
|
@@ -116,6 +117,22 @@ The API client includes many more functions tailored to the Violet Controller:
|
|
|
116
117
|
|
|
117
118
|
For a full list of available commands and more detailed examples, please refer to the [Wiki](https://github.com/Xerolux/violet-poolController-api/wiki) or the source code in `api.py`.
|
|
118
119
|
|
|
120
|
+
## Violet Dosing Standalone Mode
|
|
121
|
+
|
|
122
|
+
If your Violet setup runs as dosing standalone (without the base module), enable:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
api = VioletPoolAPI(
|
|
126
|
+
host="192.168.1.100",
|
|
127
|
+
username="admin",
|
|
128
|
+
password="your_password",
|
|
129
|
+
session=session,
|
|
130
|
+
dosing_standalone=True,
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
In this mode, dosing functions (for example `manual_dosing` and dosing parameter/target updates) stay available, while base-module-only switch functions (for example pump/light/backwash) are blocked with a clear error message.
|
|
135
|
+
|
|
119
136
|
## License
|
|
120
137
|
GNU Affero General Public License v3.0 or later (AGPLv3+)
|
|
121
138
|
|
{violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.6}/violet_poolcontroller_api/api.py
RENAMED
|
@@ -79,18 +79,19 @@ class VioletPoolAPI:
|
|
|
79
79
|
endpoints.
|
|
80
80
|
"""
|
|
81
81
|
|
|
82
|
-
def __init__(
|
|
83
|
-
self,
|
|
84
|
-
*,
|
|
85
|
-
host: str,
|
|
86
|
-
session: aiohttp.ClientSession,
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
*,
|
|
85
|
+
host: str,
|
|
86
|
+
session: aiohttp.ClientSession,
|
|
87
87
|
username: str | None = None,
|
|
88
88
|
password: str | None = None,
|
|
89
|
-
use_ssl: bool = False,
|
|
90
|
-
verify_ssl: bool = True,
|
|
91
|
-
timeout: int = 10,
|
|
92
|
-
max_retries: int = 3,
|
|
93
|
-
|
|
89
|
+
use_ssl: bool = False,
|
|
90
|
+
verify_ssl: bool = True,
|
|
91
|
+
timeout: int = 10,
|
|
92
|
+
max_retries: int = 3,
|
|
93
|
+
dosing_standalone: bool = False,
|
|
94
|
+
) -> None:
|
|
94
95
|
"""Initializes the API helper.
|
|
95
96
|
|
|
96
97
|
Args:
|
|
@@ -99,10 +100,12 @@ class VioletPoolAPI:
|
|
|
99
100
|
username: The username for authentication.
|
|
100
101
|
password: The password for authentication.
|
|
101
102
|
use_ssl: Whether to use SSL for the connection.
|
|
102
|
-
verify_ssl: Whether to verify SSL certificates (security feature).
|
|
103
|
-
timeout: The request timeout in seconds.
|
|
104
|
-
max_retries: The maximum number of retries for failed requests.
|
|
105
|
-
|
|
103
|
+
verify_ssl: Whether to verify SSL certificates (security feature).
|
|
104
|
+
timeout: The request timeout in seconds.
|
|
105
|
+
max_retries: The maximum number of retries for failed requests.
|
|
106
|
+
dosing_standalone: Whether the controller runs in dosing-standalone
|
|
107
|
+
mode without a connected base module.
|
|
108
|
+
"""
|
|
106
109
|
if session is None:
|
|
107
110
|
raise ValueError("A valid aiohttp session must be provided")
|
|
108
111
|
|
|
@@ -110,15 +113,16 @@ class VioletPoolAPI:
|
|
|
110
113
|
|
|
111
114
|
self._session = session
|
|
112
115
|
total_timeout = max(float(timeout), 1.0)
|
|
113
|
-
self._timeout = aiohttp.ClientTimeout(
|
|
114
|
-
total=total_timeout,
|
|
115
|
-
connect=total_timeout * 0.8,
|
|
116
|
-
sock_connect=total_timeout * 0.8,
|
|
117
|
-
)
|
|
118
|
-
self._max_retries = max(1, int(max_retries))
|
|
119
|
-
self.
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
self._timeout = aiohttp.ClientTimeout(
|
|
117
|
+
total=total_timeout,
|
|
118
|
+
connect=total_timeout * 0.8,
|
|
119
|
+
sock_connect=total_timeout * 0.8,
|
|
120
|
+
)
|
|
121
|
+
self._max_retries = max(1, int(max_retries))
|
|
122
|
+
self._dosing_standalone = bool(dosing_standalone)
|
|
123
|
+
self._auth = None
|
|
124
|
+
if username:
|
|
125
|
+
self._auth = aiohttp.BasicAuth(username, password or "")
|
|
122
126
|
|
|
123
127
|
# SSL/TLS security configuration
|
|
124
128
|
self._verify_ssl = verify_ssl
|
|
@@ -158,13 +162,18 @@ class VioletPoolAPI:
|
|
|
158
162
|
return self._timeout.total or 0.0
|
|
159
163
|
|
|
160
164
|
@property
|
|
161
|
-
def max_retries(self) -> int:
|
|
162
|
-
"""Get maximum retry attempts.
|
|
165
|
+
def max_retries(self) -> int:
|
|
166
|
+
"""Get maximum retry attempts.
|
|
163
167
|
|
|
164
168
|
Returns:
|
|
165
169
|
The maximum number of retry attempts.
|
|
166
|
-
"""
|
|
167
|
-
return self._max_retries
|
|
170
|
+
"""
|
|
171
|
+
return self._max_retries
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def dosing_standalone(self) -> bool:
|
|
175
|
+
"""Return whether dosing-standalone mode is enabled."""
|
|
176
|
+
return self._dosing_standalone
|
|
168
177
|
|
|
169
178
|
# ---------------------------------------------------------------------
|
|
170
179
|
# Generic helpers
|
|
@@ -399,9 +408,9 @@ class VioletPoolAPI:
|
|
|
399
408
|
)
|
|
400
409
|
return response
|
|
401
410
|
|
|
402
|
-
def _sanitize_config_payload(self, config: Mapping[str, Any]) -> dict[str, Any]:
|
|
403
|
-
"""Sanitize and validate configuration payload before POSTing it."""
|
|
404
|
-
sanitized_config: dict[str, str | int | float] = {}
|
|
411
|
+
def _sanitize_config_payload(self, config: Mapping[str, Any]) -> dict[str, Any]:
|
|
412
|
+
"""Sanitize and validate configuration payload before POSTing it."""
|
|
413
|
+
sanitized_config: dict[str, str | int | float] = {}
|
|
405
414
|
|
|
406
415
|
for key, value in config.items():
|
|
407
416
|
try:
|
|
@@ -427,7 +436,31 @@ class VioletPoolAPI:
|
|
|
427
436
|
f"Invalid configuration parameter: {key}"
|
|
428
437
|
) from err
|
|
429
438
|
|
|
430
|
-
return sanitized_config
|
|
439
|
+
return sanitized_config
|
|
440
|
+
|
|
441
|
+
def _is_base_module_function(self, key: str) -> bool:
|
|
442
|
+
"""Return True if the function depends on the base module."""
|
|
443
|
+
normalized = (key or "").strip().upper()
|
|
444
|
+
if not normalized:
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
if normalized.startswith("DOS_"):
|
|
448
|
+
return False
|
|
449
|
+
|
|
450
|
+
if normalized.startswith(("EXT", "DMX_SCENE", "DIRULE_", "OMNI_DC")):
|
|
451
|
+
return True
|
|
452
|
+
|
|
453
|
+
return normalized in {
|
|
454
|
+
"PUMP",
|
|
455
|
+
"SOLAR",
|
|
456
|
+
"HEATER",
|
|
457
|
+
"LIGHT",
|
|
458
|
+
"ECO",
|
|
459
|
+
"BACKWASH",
|
|
460
|
+
"BACKWASHRINSE",
|
|
461
|
+
"REFILL",
|
|
462
|
+
"PVSURPLUS",
|
|
463
|
+
}
|
|
431
464
|
|
|
432
465
|
# ---------------------------------------------------------------------
|
|
433
466
|
# Public API surface
|
|
@@ -640,8 +673,9 @@ class VioletPoolAPI:
|
|
|
640
673
|
"Skipping malformed calibration history line: %s", line
|
|
641
674
|
)
|
|
642
675
|
except (IndexError, AttributeError) as err:
|
|
676
|
+
err_msg = str(err) or type(err).__name__
|
|
643
677
|
_LOGGER.warning(
|
|
644
|
-
"Error parsing calibration history line '%s': %s", line,
|
|
678
|
+
"Error parsing calibration history line '%s': %s", line, err_msg
|
|
645
679
|
)
|
|
646
680
|
return entries
|
|
647
681
|
|
|
@@ -701,10 +735,10 @@ class VioletPoolAPI:
|
|
|
701
735
|
)
|
|
702
736
|
return self._command_result(body)
|
|
703
737
|
|
|
704
|
-
async def set_switch_state(
|
|
705
|
-
self,
|
|
706
|
-
key: str,
|
|
707
|
-
action: str,
|
|
738
|
+
async def set_switch_state(
|
|
739
|
+
self,
|
|
740
|
+
key: str,
|
|
741
|
+
action: str,
|
|
708
742
|
*,
|
|
709
743
|
duration: int | float | None = None,
|
|
710
744
|
last_value: int | float | None = None,
|
|
@@ -717,12 +751,18 @@ class VioletPoolAPI:
|
|
|
717
751
|
duration: An optional duration for the action.
|
|
718
752
|
last_value: An optional last value (e.g., speed).
|
|
719
753
|
|
|
720
|
-
Returns:
|
|
721
|
-
A dictionary with the command result.
|
|
722
|
-
"""
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
754
|
+
Returns:
|
|
755
|
+
A dictionary with the command result.
|
|
756
|
+
"""
|
|
757
|
+
if self._dosing_standalone and self._is_base_module_function(key):
|
|
758
|
+
raise VioletPoolAPIError(
|
|
759
|
+
f"Function '{key}' requires the Violet base module and is not "
|
|
760
|
+
"available in dosing-standalone mode"
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
payload = self._build_manual_command(
|
|
764
|
+
key,
|
|
765
|
+
action,
|
|
726
766
|
duration=duration,
|
|
727
767
|
last_value=last_value,
|
|
728
768
|
)
|
|
@@ -803,7 +843,7 @@ class VioletPoolAPI:
|
|
|
803
843
|
results: list[dict[str, Any]] = []
|
|
804
844
|
for res in raw_results:
|
|
805
845
|
if isinstance(res, Exception):
|
|
806
|
-
results.append({"success": False, "response": str(res)})
|
|
846
|
+
results.append({"success": False, "response": f"{type(res).__name__}: {str(res)}"})
|
|
807
847
|
elif isinstance(res, dict):
|
|
808
848
|
results.append(res)
|
|
809
849
|
|
|
@@ -22,12 +22,13 @@ import asyncio
|
|
|
22
22
|
import logging
|
|
23
23
|
import time
|
|
24
24
|
from collections.abc import Callable
|
|
25
|
+
from enum import StrEnum
|
|
25
26
|
from typing import Any
|
|
26
27
|
|
|
27
28
|
_LOGGER = logging.getLogger(__name__)
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
class CircuitBreakerState:
|
|
31
|
+
class CircuitBreakerState(StrEnum):
|
|
31
32
|
"""Circuit breaker states."""
|
|
32
33
|
|
|
33
34
|
CLOSED = "CLOSED" # Normal operation
|
|
@@ -30,7 +30,7 @@ from typing import Any, cast
|
|
|
30
30
|
# DEVICE PARAMETERS - Extended Configuration
|
|
31
31
|
# =============================================================================
|
|
32
32
|
|
|
33
|
-
DEVICE_PARAMETERS = {
|
|
33
|
+
DEVICE_PARAMETERS: dict[str, dict[str, Any]] = {
|
|
34
34
|
"PUMP": {
|
|
35
35
|
"supports_speed": True,
|
|
36
36
|
"api_template": "PUMP,{action},{duration},{speed}",
|
|
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
|