violet-poolController-api 0.0.5__tar.gz → 0.0.9__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.
Files changed (19) hide show
  1. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/PKG-INFO +22 -2
  2. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/README.md +122 -102
  3. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/pyproject.toml +1 -1
  4. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/setup.py +1 -1
  5. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/tests/test_api.py +181 -92
  6. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolController_api.egg-info/PKG-INFO +22 -2
  7. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/api.py +1058 -985
  8. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/circuit_breaker.py +2 -1
  9. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/const_devices.py +1 -1
  10. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/utils_rate_limiter.py +9 -13
  11. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/utils_sanitizer.py +12 -9
  12. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/LICENSE +0 -0
  13. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/setup.cfg +0 -0
  14. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolController_api.egg-info/SOURCES.txt +0 -0
  15. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
  16. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolController_api.egg-info/requires.txt +0 -0
  17. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolController_api.egg-info/top_level.txt +0 -0
  18. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/__init__.py +0 -0
  19. {violet_poolcontroller_api-0.0.5 → violet_poolcontroller_api-0.0.9}/violet_poolcontroller_api/const_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: violet-poolController-api
3
- Version: 0.0.5
3
+ Version: 0.0.9
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,25 @@ 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
+
136
+ **Note on getReadings format:**
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
+
119
139
  ## License
120
140
  GNU Affero General Public License v3.0 or later (AGPLv3+)
121
141
 
@@ -1,107 +1,127 @@
1
- # Violet Pool Controller API
2
-
3
- [![PyPI version](https://img.shields.io/pypi/v/violet-poolController-api.svg?style=for-the-badge)](https://pypi.org/project/violet-poolController-api/)
4
- [![PyPI downloads](https://img.shields.io/pypi/dm/violet-poolController-api.svg?style=for-the-badge)](https://pypistats.org/packages/violet-poolcontroller-api)
5
- [![Python versions](https://img.shields.io/pypi/pyversions/violet-poolController-api.svg?style=for-the-badge)](https://pypi.org/project/violet-poolController-api/)
6
- [![License: AGPL v3+](https://img.shields.io/badge/License-AGPL_v3%2B-blue?style=for-the-badge)](LICENSE)
7
-
8
- [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-xerolux-yellow?logo=buy-me-a-coffee&style=for-the-badge)](https://www.buymeacoffee.com/xerolux)
9
- [![Tesla](https://img.shields.io/badge/Tesla-Referral-red?style=for-the-badge&logo=tesla)](https://ts.la/sebastian564489)
10
-
11
- An asynchronous Python client for interacting with the **Violet Pool Controller**.
12
-
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
-
1
+ # Violet Pool Controller API
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/violet-poolController-api.svg?style=for-the-badge)](https://pypi.org/project/violet-poolController-api/)
4
+ [![PyPI downloads](https://img.shields.io/pypi/dm/violet-poolController-api.svg?style=for-the-badge)](https://pypistats.org/packages/violet-poolcontroller-api)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/violet-poolController-api.svg?style=for-the-badge)](https://pypi.org/project/violet-poolController-api/)
6
+ [![License: AGPL v3+](https://img.shields.io/badge/License-AGPL_v3%2B-blue?style=for-the-badge)](LICENSE)
7
+
8
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-xerolux-yellow?logo=buy-me-a-coffee&style=for-the-badge)](https://www.buymeacoffee.com/xerolux)
9
+ [![Tesla](https://img.shields.io/badge/Tesla-Referral-red?style=for-the-badge&logo=tesla)](https://ts.la/sebastian564489)
10
+
11
+ An asynchronous Python client for interacting with the **Violet Pool Controller**.
12
+
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
+
15
15
  > **📖 Documentation:**
16
16
  > - GitHub Pages: https://xerolux.github.io/violet-poolController-api/
17
17
  > - GitHub Wiki: https://github.com/Xerolux/violet-poolController-api/wiki
18
18
  >
19
19
  > The `docs/` directory is the single source of truth and is used for both GitHub Pages and Wiki sync.
20
-
21
- ## Features
22
- * **Asynchronous:** Fully async operations using `aiohttp`.
23
- * **Resilient:** Built-in Circuit Breaker and Rate Limiter to protect both the client and the controller from overload.
24
- * **Sanitization:** Strict payload input sanitization to prevent injection and invalid settings.
25
-
26
- ## Installation
27
-
28
- ```bash
29
- pip install violet-poolController-api
30
- ```
31
-
32
- ## Basic Usage
33
-
34
- ```python
35
- import asyncio
36
- import aiohttp
37
- from violet_poolcontroller_api.api import VioletPoolAPI, VioletPoolAPIError
38
-
39
- async def main():
40
- # Create an aiohttp ClientSession
41
- async with aiohttp.ClientSession() as session:
42
- # Initialize the API
43
- # Note: In a standard setup, just enter the IP address without a port.
44
- # A port (e.g. "192.168.1.100:8080") can optionally be provided if you use a proxy or alternative setup.
45
- api = VioletPoolAPI(
46
- host="192.168.1.100",
47
- username="admin",
48
- password="your_password",
49
- session=session
50
- )
51
-
52
- try:
53
- # --- 1. Fetch current sensor readings ---
54
- readings = await api.get_readings()
55
- print("Current Pool Readings:")
56
- print(readings)
57
-
58
- # --- 2. Control the Filter Pump ---
59
- # Set pump speed to 2 (Normal) permanently (duration=0)
60
- await api.set_pump_speed(speed=2, duration=0)
61
- print("\nPump speed set to 2.")
62
-
63
- # --- 3. Set Target Temperature ---
64
- # Set the target temperature for the heater to 28.5 degrees
65
- await api.set_device_temperature("HEATER", 28.5)
66
- print("\nHeater target temperature set to 28.5°C.")
67
-
68
- # --- 4. Control Pool Lights ---
69
- # Trigger the color pulse animation for the pool light
70
- await api.set_light_color_pulse()
71
- print("\nLight color pulse triggered.")
72
-
73
- except VioletPoolAPIError as e:
74
- print(f"An error occurred while communicating with the Violet controller: {e}")
75
-
76
- if __name__ == "__main__":
77
- asyncio.run(main())
78
- ```
79
-
80
- ## Advanced Operations
81
-
82
- The API client includes many more functions tailored to the Violet Controller:
83
- - `get_config(["PUMP_SPEED_1", "PUMP_SPEED_2"])`: Fetch specific configuration values.
84
- - `set_ph_target(7.2)`: Change the pH target value.
85
- - `set_orp_target(750)`: Change the ORP (Redox) target value.
86
- - `set_pv_surplus(active=True)`: Enable the PV-Surplus mode.
87
- - `manual_dosing(dosing_type="Chlor", duration=120)`: Trigger manual chemical dosing.
88
-
89
- 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
- ## License
92
- GNU Affero General Public License v3.0 or later (AGPLv3+)
93
-
94
- ---
95
-
96
- ## About the Violet Pool Controller
97
-
98
- Der **VIOLET Pool Controller** von [PoolDigital GmbH & Co. KG](https://www.pooldigital.de/) ist ein Premium Smart Pool Automation System aus deutscher Entwicklung – mit JSON API für nahtlose Home Assistant Integration.
99
-
100
- - **Offizieller Shop:** [pooldigital.de](https://www.pooldigital.de/)
101
- - **Community:** [PoolDigital Forum](http://forum.pooldigital.de/)
102
-
103
- **Disclaimer:**
104
- *This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or associated with PoolDigital GmbH & Co. KG in any way. "VIOLET" and any related trademarks are the property of their respective owners.*
105
-
106
- ⚠️ **WARNING - USE AT YOUR OWN RISK:**
107
- *This software interacts with physical hardware and automation systems that control water chemistry (pH, Chlorine/ORP) and electrical equipment (pumps, heaters). A bug, network issue, or incorrect configuration could result in hardware damage, unsafe water conditions, or other hazards. By using this software, you acknowledge and agree that you are solely responsible for any damage, injury, or loss of property that may occur. Please always monitor your pool's chemistry and hardware independently.*
20
+
21
+ ## Features
22
+ * **Asynchronous:** Fully async operations using `aiohttp`.
23
+ * **Resilient:** Built-in Circuit Breaker and Rate Limiter to protect both the client and the controller from overload.
24
+ * **Sanitization:** Strict payload input sanitization to prevent injection and invalid settings.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install violet-poolController-api
30
+ ```
31
+
32
+ ## Basic Usage
33
+
34
+ ```python
35
+ import asyncio
36
+ import aiohttp
37
+ from violet_poolcontroller_api.api import VioletPoolAPI, VioletPoolAPIError
38
+
39
+ async def main():
40
+ # Create an aiohttp ClientSession
41
+ async with aiohttp.ClientSession() as session:
42
+ # Initialize the API
43
+ # Note: In a standard setup, just enter the IP address without a port.
44
+ # A port (e.g. "192.168.1.100:8080") can optionally be provided if you use a proxy or alternative setup.
45
+ api = VioletPoolAPI(
46
+ host="192.168.1.100",
47
+ username="admin",
48
+ password="your_password",
49
+ session=session,
50
+ dosing_standalone=False, # True for Violet dosing standalone setups
51
+ )
52
+
53
+ try:
54
+ # --- 1. Fetch current sensor readings ---
55
+ readings = await api.get_readings()
56
+ print("Current Pool Readings:")
57
+ print(readings)
58
+
59
+ # --- 2. Control the Filter Pump ---
60
+ # Set pump speed to 2 (Normal) permanently (duration=0)
61
+ await api.set_pump_speed(speed=2, duration=0)
62
+ print("\nPump speed set to 2.")
63
+
64
+ # --- 3. Set Target Temperature ---
65
+ # Set the target temperature for the heater to 28.5 degrees
66
+ await api.set_device_temperature("HEATER", 28.5)
67
+ print("\nHeater target temperature set to 28.5°C.")
68
+
69
+ # --- 4. Control Pool Lights ---
70
+ # Trigger the color pulse animation for the pool light
71
+ await api.set_light_color_pulse()
72
+ print("\nLight color pulse triggered.")
73
+
74
+ except VioletPoolAPIError as e:
75
+ print(f"An error occurred while communicating with the Violet controller: {e}")
76
+
77
+ if __name__ == "__main__":
78
+ asyncio.run(main())
79
+ ```
80
+
81
+ ## Advanced Operations
82
+
83
+ The API client includes many more functions tailored to the Violet Controller:
84
+ - `get_config(["PUMP_SPEED_1", "PUMP_SPEED_2"])`: Fetch specific configuration values.
85
+ - `set_ph_target(7.2)`: Change the pH target value.
86
+ - `set_orp_target(750)`: Change the ORP (Redox) target value.
87
+ - `set_pv_surplus(active=True)`: Enable the PV-Surplus mode.
88
+ - `manual_dosing(dosing_type="Chlor", duration=120)`: Trigger manual chemical dosing.
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`.
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
+
108
+ **Note on getReadings format:**
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
+
111
+ ## License
112
+ GNU Affero General Public License v3.0 or later (AGPLv3+)
113
+
114
+ ---
115
+
116
+ ## About the Violet Pool Controller
117
+
118
+ Der **VIOLET Pool Controller** von [PoolDigital GmbH & Co. KG](https://www.pooldigital.de/) ist ein Premium Smart Pool Automation System aus deutscher Entwicklung – mit JSON API für nahtlose Home Assistant Integration.
119
+
120
+ - **Offizieller Shop:** [pooldigital.de](https://www.pooldigital.de/)
121
+ - **Community:** [PoolDigital Forum](http://forum.pooldigital.de/)
122
+
123
+ **Disclaimer:**
124
+ *This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or associated with PoolDigital GmbH & Co. KG in any way. "VIOLET" and any related trademarks are the property of their respective owners.*
125
+
126
+ ⚠️ **WARNING - USE AT YOUR OWN RISK:**
127
+ *This software interacts with physical hardware and automation systems that control water chemistry (pH, Chlorine/ORP) and electrical equipment (pumps, heaters). A bug, network issue, or incorrect configuration could result in hardware damage, unsafe water conditions, or other hazards. By using this software, you acknowledge and agree that you are solely responsible for any damage, injury, or loss of property that may occur. Please always monitor your pool's chemistry and hardware independently.*
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "violet-poolController-api"
7
- version = "0.0.5"
7
+ version = "0.0.9"
8
8
  authors = [
9
9
  { name="Basti (Xerolux)", email="git@xerolux.de" },
10
10
  ]
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
  setup(
3
3
  name="violet-poolController-api",
4
- version="0.0.5",
4
+ version="0.0.9",
5
5
  author="Basti (Xerolux)",
6
6
  author_email="git@xerolux.de",
7
7
  description="Asynchronous Python client for the Violet Pool Controller.",
@@ -1,102 +1,116 @@
1
- # violet-poolController-api - API für Violet Pool Controller
2
- # Copyright (C) 2024–2026 Xerolux
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU Affero General Public License as published
6
- # by the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU Affero General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Affero General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
1
+ # violet-poolController-api - API für Violet Pool Controller
2
+ # Copyright (C) 2024–2026 Xerolux
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published
6
+ # by the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
17
  import aiohttp
18
18
  import pytest
19
19
  import pytest_asyncio
20
20
  from aioresponses import aioresponses
21
21
  from violet_poolcontroller_api.api import VioletPoolAPI, VioletPoolAPIError
22
22
  from violet_poolcontroller_api.circuit_breaker import CircuitBreakerOpenError
23
-
24
- @pytest.fixture
25
- def mock_aioresponse():
26
- with aioresponses() as m:
27
- yield m
28
-
29
- @pytest_asyncio.fixture
30
- async def api_client():
31
- async with aiohttp.ClientSession() as session:
32
- # Pass low retry counts to make error tests faster
33
- api = VioletPoolAPI(
34
- host="192.168.1.100",
35
- session=session,
36
- username="admin",
37
- password="password",
38
- max_retries=1
39
- )
40
- yield api
41
-
42
- @pytest.mark.asyncio
43
- async def test_get_readings_success(mock_aioresponse, api_client):
44
- """Test get_readings returns the correct parsed JSON dictionary."""
45
- url = "http://192.168.1.100/getReadings?ALL"
46
- mock_data = {"PUMPSTATE": "2", "PH": 7.2}
47
- mock_aioresponse.get(url, payload=mock_data, status=200)
48
-
49
- result = await api_client.get_readings()
50
-
51
- assert isinstance(result, dict)
52
- assert result == mock_data
53
-
54
- @pytest.mark.asyncio
55
- async def test_set_pump_speed_success(mock_aioresponse, api_client):
56
- """Test set_pump_speed formats the request correctly and returns success."""
57
- url = "http://192.168.1.100/setFunctionManually?PUMP,ON,0,2"
58
- mock_aioresponse.get(url, body="OK", status=200)
59
-
60
- result = await api_client.set_pump_speed(speed=2, duration=0)
61
-
62
- assert result["success"] is True
63
- assert result["response"] == "OK"
64
-
65
- @pytest.mark.asyncio
66
- async def test_request_server_error(mock_aioresponse, api_client):
67
- """Test that a 500 error raises VioletPoolAPIError after retrying."""
68
- url = "http://192.168.1.100/getReadings?ALL"
69
- mock_aioresponse.get(url, status=500)
70
- # the second time it retries
71
- mock_aioresponse.get(url, status=500)
72
-
73
- with pytest.raises(VioletPoolAPIError) as exc_info:
74
- await api_client.get_readings()
75
-
76
- assert "Error communicating with Violet controller" in str(exc_info.value)
77
-
78
- @pytest.mark.asyncio
79
- async def test_init_with_port():
80
- """Test initializing API with a port in the hostname."""
81
- async with aiohttp.ClientSession() as session:
82
- api = VioletPoolAPI(
83
- host="192.168.1.100:8080",
84
- session=session,
85
- username="admin",
86
- password="password"
87
- )
88
- assert api._base_url == "http://192.168.1.100:8080"
89
-
90
-
91
- @pytest.mark.asyncio
23
+
24
+ @pytest.fixture
25
+ def mock_aioresponse():
26
+ with aioresponses() as m:
27
+ yield m
28
+
29
+ @pytest_asyncio.fixture
30
+ async def api_client():
31
+ async with aiohttp.ClientSession() as session:
32
+ # Pass low retry counts to make error tests faster
33
+ api = VioletPoolAPI(
34
+ host="192.168.1.100",
35
+ session=session,
36
+ username="admin",
37
+ password="password",
38
+ max_retries=1
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
+
56
+ @pytest.mark.asyncio
57
+ async def test_get_readings_success(mock_aioresponse, api_client):
58
+ """Test get_readings returns the correct parsed JSON dictionary."""
59
+ url = "http://192.168.1.100/getReadings?ALL"
60
+ mock_data = {"PUMPSTATE": "2", "PH": 7.2}
61
+ mock_aioresponse.get(url, payload=mock_data, status=200)
62
+
63
+ result = await api_client.get_readings()
64
+
65
+ assert isinstance(result, dict)
66
+ assert result == mock_data
67
+
68
+ @pytest.mark.asyncio
69
+ async def test_set_pump_speed_success(mock_aioresponse, api_client):
70
+ """Test set_pump_speed formats the request correctly and returns success."""
71
+ url = "http://192.168.1.100/setFunctionManually?PUMP,ON,0,2"
72
+ mock_aioresponse.get(url, body="OK", status=200)
73
+
74
+ result = await api_client.set_pump_speed(speed=2, duration=0)
75
+
76
+ assert result["success"] is True
77
+ assert result["response"] == "OK"
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_request_server_error(mock_aioresponse, api_client):
81
+ """Test that a 500 error raises VioletPoolAPIError after retrying."""
82
+ url = "http://192.168.1.100/getReadings?ALL"
83
+ mock_aioresponse.get(url, status=500)
84
+ # the second time it retries
85
+ mock_aioresponse.get(url, status=500)
86
+
87
+ with pytest.raises(VioletPoolAPIError) as exc_info:
88
+ await api_client.get_readings()
89
+
90
+ assert "Error communicating with Violet controller" in str(exc_info.value)
91
+
92
+ @pytest.mark.asyncio
93
+ async def test_init_with_port():
94
+ """Test initializing API with a port in the hostname."""
95
+ async with aiohttp.ClientSession() as session:
96
+ api = VioletPoolAPI(
97
+ host="192.168.1.100:8080",
98
+ session=session,
99
+ username="admin",
100
+ password="password"
101
+ )
102
+ assert api._base_url == "http://192.168.1.100:8080"
103
+
104
+
105
+ @pytest.mark.asyncio
92
106
  async def test_circuit_breaker_open_is_wrapped(api_client, monkeypatch):
93
- """Test circuit breaker open errors are exposed as VioletPoolAPIError."""
94
-
95
- async def raise_open(_func, *args, **kwargs):
96
- raise CircuitBreakerOpenError("Circuit breaker is OPEN")
97
-
98
- monkeypatch.setattr(api_client._circuit_breaker, "call", raise_open)
99
-
107
+ """Test circuit breaker open errors are exposed as VioletPoolAPIError."""
108
+
109
+ async def raise_open(_func, *args, **kwargs):
110
+ raise CircuitBreakerOpenError("Circuit breaker is OPEN")
111
+
112
+ monkeypatch.setattr(api_client._circuit_breaker, "call", raise_open)
113
+
100
114
  with pytest.raises(VioletPoolAPIError) as exc_info:
101
115
  await api_client.get_readings()
102
116
 
@@ -165,3 +179,78 @@ 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)
205
+
206
+ @pytest.mark.asyncio
207
+ async def test_get_readings_standalone_list_format(mock_aioresponse, api_client):
208
+ """Test get_readings parses the standalone list format correctly."""
209
+ url = "http://192.168.1.100/getReadings?ALL"
210
+ mock_data = {
211
+ "getReadings": [
212
+ {
213
+ "VALUE NAME": " \"date\"",
214
+ "DESCRIPTION": "System-date",
215
+ "FORMAT": "STRING",
216
+ "DETAILS": "deliverd as TT.MM.YYYY",
217
+ "VALUE": "12.04.2023"
218
+ },
219
+ {
220
+ "VALUE NAME": " \"CPU_TEMP\"",
221
+ "DESCRIPTION": "CPU-Temperature",
222
+ "FORMAT": "FLOAT",
223
+ "DETAILS": None,
224
+ "VALUE": 45.5
225
+ }
226
+ ]
227
+ }
228
+ mock_aioresponse.get(url, payload=mock_data, status=200)
229
+
230
+ assert api_client.dosing_standalone is False
231
+
232
+ result = await api_client.get_readings()
233
+
234
+ assert isinstance(result, dict)
235
+ assert result == {"date": "12.04.2023", "CPU_TEMP": 45.5}
236
+ assert api_client.dosing_standalone is True
237
+
238
+
239
+ @pytest.mark.asyncio
240
+ async def test_dosing_standalone_detection_dict_format(mock_aioresponse, standalone_api_client):
241
+ """Test dosing_standalone is set to False when dict format is received."""
242
+ url = "http://192.168.1.100/getReadings?ALL"
243
+ mock_data = {
244
+ "getReadings": {
245
+ "PUMPSTATE": "2",
246
+ "PH": 7.2
247
+ }
248
+ }
249
+ mock_aioresponse.get(url, payload=mock_data, status=200)
250
+
251
+ assert standalone_api_client.dosing_standalone is True
252
+
253
+ result = await standalone_api_client.get_readings()
254
+
255
+ assert isinstance(result, dict)
256
+ assert standalone_api_client.dosing_standalone is False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: violet-poolController-api
3
- Version: 0.0.5
3
+ Version: 0.0.9
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,25 @@ 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
+
136
+ **Note on getReadings format:**
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
+
119
139
  ## License
120
140
  GNU Affero General Public License v3.0 or later (AGPLv3+)
121
141