violet-poolController-api 0.0.22__tar.gz → 0.0.24__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.22/violet_poolController_api.egg-info → violet_poolcontroller_api-0.0.24}/PKG-INFO +1 -1
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/pyproject.toml +3 -3
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/setup.py +3 -2
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/tests/test_api.py +16 -2
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24/violet_poolController_api.egg-info}/PKG-INFO +1 -1
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/api.py +121 -9
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/const_api.py +18 -5
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/LICENSE +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/README.md +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/setup.cfg +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/tests/__init__.py +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolController_api.egg-info/SOURCES.txt +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolController_api.egg-info/requires.txt +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolController_api.egg-info/top_level.txt +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/__init__.py +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/circuit_breaker.py +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/const_devices.py +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/utils_rate_limiter.py +0 -0
- {violet_poolcontroller_api-0.0.22 → violet_poolcontroller_api-0.0.24}/violet_poolcontroller_api/utils_sanitizer.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "violet-poolController-api"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.24"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Basti (Xerolux)", email="git@xerolux.de" },
|
|
10
10
|
]
|
|
@@ -45,13 +45,13 @@ asyncio_mode = "auto"
|
|
|
45
45
|
|
|
46
46
|
[tool.ruff]
|
|
47
47
|
line-length = 100
|
|
48
|
-
target-version = "0.0.
|
|
48
|
+
target-version = "0.0.24"
|
|
49
49
|
|
|
50
50
|
[tool.ruff.lint]
|
|
51
51
|
select = ["E", "F", "W", "I", "UP"]
|
|
52
52
|
ignore = ["E501"]
|
|
53
53
|
|
|
54
54
|
[tool.mypy]
|
|
55
|
-
python_version = "0.0.
|
|
55
|
+
python_version = "0.0.24"
|
|
56
56
|
warn_return_any = true
|
|
57
57
|
warn_unused_configs = true
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from setuptools import
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
2
3
|
setup(
|
|
3
4
|
name="violet-poolController-api",
|
|
4
|
-
version="0.0.
|
|
5
|
+
version="0.0.24",
|
|
5
6
|
author="Basti (Xerolux)",
|
|
6
7
|
author_email="git@xerolux.de",
|
|
7
8
|
description="Asynchronous Python client for the Violet Pool Controller.",
|
|
@@ -784,6 +784,20 @@ async def test_set_device_temperature(
|
|
|
784
784
|
assert result["success"] is True
|
|
785
785
|
|
|
786
786
|
|
|
787
|
+
@pytest.mark.asyncio
|
|
788
|
+
async def test_set_device_temperature_solar(
|
|
789
|
+
mock_aioresponse: aioresponses,
|
|
790
|
+
api_client: VioletPoolAPI,
|
|
791
|
+
) -> None:
|
|
792
|
+
"""Test set_device_temperature uses SOLAR_maxtemp for SOLAR key."""
|
|
793
|
+
url = "http://192.168.1.100/setConfig"
|
|
794
|
+
mock_aioresponse.post(url, body="OK", status=200)
|
|
795
|
+
|
|
796
|
+
result = await api_client.set_device_temperature("SOLAR", 30.0)
|
|
797
|
+
|
|
798
|
+
assert result["success"] is True
|
|
799
|
+
|
|
800
|
+
|
|
787
801
|
@pytest.mark.asyncio
|
|
788
802
|
async def test_set_ph_target(
|
|
789
803
|
mock_aioresponse: aioresponses,
|
|
@@ -845,8 +859,8 @@ async def test_set_dosing_parameters(
|
|
|
845
859
|
mock_aioresponse: aioresponses,
|
|
846
860
|
api_client: VioletPoolAPI,
|
|
847
861
|
) -> None:
|
|
848
|
-
"""Test set_dosing_parameters sends POST
|
|
849
|
-
url = "http://192.168.1.100/
|
|
862
|
+
"""Test set_dosing_parameters sends POST via /setConfig."""
|
|
863
|
+
url = "http://192.168.1.100/setConfig"
|
|
850
864
|
mock_aioresponse.post(url, body="OK", status=200)
|
|
851
865
|
|
|
852
866
|
result = await api_client.set_dosing_parameters({"DOS_1_CL_DOSING_TIME": 30})
|
|
@@ -42,6 +42,8 @@ from .const_api import (
|
|
|
42
42
|
API_GET_CALIB_RAW_VALUES,
|
|
43
43
|
API_GET_CONFIG,
|
|
44
44
|
API_GET_HISTORY,
|
|
45
|
+
API_GET_LOG,
|
|
46
|
+
API_GET_NOTIFICATIONS,
|
|
45
47
|
API_GET_OUTPUT_STATES,
|
|
46
48
|
API_GET_OVERALL_DOSING,
|
|
47
49
|
API_GET_WEATHER_DATA,
|
|
@@ -50,10 +52,10 @@ from .const_api import (
|
|
|
50
52
|
API_READINGS,
|
|
51
53
|
API_RESTORE_CALIBRATION,
|
|
52
54
|
API_SET_CONFIG,
|
|
53
|
-
API_SET_DOSING_PARAMETERS,
|
|
54
55
|
API_SET_FUNCTION_MANUALLY,
|
|
55
56
|
API_SET_OUTPUT_TESTMODE,
|
|
56
57
|
API_TRIGGER_MANUAL_DOSING,
|
|
58
|
+
DOSING_CONFIG_PREFIX,
|
|
57
59
|
DOSING_FUNCTIONS,
|
|
58
60
|
DOSING_OUTPUT_INDEX,
|
|
59
61
|
ERROR_CODES,
|
|
@@ -1117,8 +1119,8 @@ class VioletPoolAPI:
|
|
|
1117
1119
|
A dictionary with the command result.
|
|
1118
1120
|
|
|
1119
1121
|
"""
|
|
1120
|
-
|
|
1121
|
-
return await self.set_target_value(
|
|
1122
|
+
config_key = "SOLAR_maxtemp" if climate_key.upper() == "SOLAR" else f"{climate_key}_set_temp"
|
|
1123
|
+
return await self.set_target_value(config_key, float(temperature))
|
|
1122
1124
|
|
|
1123
1125
|
async def set_ph_target(self, value: float) -> dict[str, Any]:
|
|
1124
1126
|
"""Update the pH setpoint.
|
|
@@ -1173,7 +1175,11 @@ class VioletPoolAPI:
|
|
|
1173
1175
|
self,
|
|
1174
1176
|
parameters: Mapping[str, Any],
|
|
1175
1177
|
) -> dict[str, Any]:
|
|
1176
|
-
"""Update dosing parameters via
|
|
1178
|
+
"""Update dosing parameters via /setConfig.
|
|
1179
|
+
|
|
1180
|
+
The /setDosingParameters endpoint does not exist on the controller
|
|
1181
|
+
(firmware 1.1.9). All dosing parameters are written through
|
|
1182
|
+
POST /setConfig, just like other configuration values.
|
|
1177
1183
|
|
|
1178
1184
|
Args:
|
|
1179
1185
|
parameters: A mapping of dosing parameters.
|
|
@@ -1182,12 +1188,64 @@ class VioletPoolAPI:
|
|
|
1182
1188
|
A dictionary with the command result.
|
|
1183
1189
|
|
|
1184
1190
|
"""
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1191
|
+
return await self.set_config(dict(parameters))
|
|
1192
|
+
|
|
1193
|
+
async def set_dosage_enabled(
|
|
1194
|
+
self,
|
|
1195
|
+
dosing_type: str,
|
|
1196
|
+
enabled: bool,
|
|
1197
|
+
) -> dict[str, Any]:
|
|
1198
|
+
"""Enable or disable a dosing function.
|
|
1199
|
+
|
|
1200
|
+
Args:
|
|
1201
|
+
dosing_type: One of ``"pH-"``, ``"pH+"``, ``"Chlor"``,
|
|
1202
|
+
``"Elektrolyse"``, ``"Flockmittel"``, ``"H2O2"``.
|
|
1203
|
+
enabled: True to enable, False to disable.
|
|
1204
|
+
|
|
1205
|
+
Returns:
|
|
1206
|
+
A dictionary with the command result.
|
|
1207
|
+
|
|
1208
|
+
Raises:
|
|
1209
|
+
VioletPoolAPIError: If the dosing type is unknown.
|
|
1210
|
+
|
|
1211
|
+
"""
|
|
1212
|
+
prefix = DOSING_CONFIG_PREFIX.get(dosing_type)
|
|
1213
|
+
if prefix is None:
|
|
1214
|
+
msg = (
|
|
1215
|
+
f"Unknown dosing type '{dosing_type}'. "
|
|
1216
|
+
f"Valid: {list(DOSING_CONFIG_PREFIX)}"
|
|
1217
|
+
)
|
|
1218
|
+
raise VioletPoolAPIError(msg)
|
|
1219
|
+
|
|
1220
|
+
return await self.set_config({f"{prefix}_use": 1 if enabled else 0})
|
|
1221
|
+
|
|
1222
|
+
async def is_dosage_enabled(self, dosing_type: str) -> bool:
|
|
1223
|
+
"""Check whether a dosing function is enabled.
|
|
1224
|
+
|
|
1225
|
+
Args:
|
|
1226
|
+
dosing_type: One of ``"pH-"``, ``"pH+"``, ``"Chlor"``,
|
|
1227
|
+
``"Elektrolyse"``, ``"Flockmittel"``, ``"H2O2"``.
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
True if the dosing function is enabled.
|
|
1231
|
+
|
|
1232
|
+
Raises:
|
|
1233
|
+
VioletPoolAPIError: If the dosing type is unknown.
|
|
1234
|
+
|
|
1235
|
+
"""
|
|
1236
|
+
prefix = DOSING_CONFIG_PREFIX.get(dosing_type)
|
|
1237
|
+
if prefix is None:
|
|
1238
|
+
msg = (
|
|
1239
|
+
f"Unknown dosing type '{dosing_type}'. "
|
|
1240
|
+
f"Valid: {list(DOSING_CONFIG_PREFIX)}"
|
|
1241
|
+
)
|
|
1242
|
+
raise VioletPoolAPIError(msg)
|
|
1243
|
+
|
|
1244
|
+
result = await self._request_json_dict(
|
|
1245
|
+
API_GET_CONFIG,
|
|
1246
|
+
query=f"{prefix}_use",
|
|
1189
1247
|
)
|
|
1190
|
-
return
|
|
1248
|
+
return bool(int(result.get(f"{prefix}_use", 0)))
|
|
1191
1249
|
|
|
1192
1250
|
async def set_pump_speed(
|
|
1193
1251
|
self,
|
|
@@ -1307,3 +1365,57 @@ class VioletPoolAPI:
|
|
|
1307
1365
|
)
|
|
1308
1366
|
|
|
1309
1367
|
return results
|
|
1368
|
+
|
|
1369
|
+
async def get_log(
|
|
1370
|
+
self,
|
|
1371
|
+
log_type: str,
|
|
1372
|
+
page: int = 0,
|
|
1373
|
+
) -> dict[str, Any]:
|
|
1374
|
+
"""Fetch log entries from the controller.
|
|
1375
|
+
|
|
1376
|
+
Args:
|
|
1377
|
+
log_type: One of ``LOG_TYPE_ACTIONS``, ``LOG_TYPE_SWITCHING``,
|
|
1378
|
+
``LOG_TYPE_ONEWIRE`` (``"actions"``, ``"switching"``,
|
|
1379
|
+
``"onewire"``).
|
|
1380
|
+
page: Page number (0-based). Use -1 to download the full
|
|
1381
|
+
actions log instead of paginated text.
|
|
1382
|
+
|
|
1383
|
+
Returns:
|
|
1384
|
+
A dict with keys:
|
|
1385
|
+
- ``lines``: list of pipe-delimited log line strings
|
|
1386
|
+
- ``has_more``: True when ``LOAD_MORE`` sentinel was present
|
|
1387
|
+
- ``raw``: the raw text response
|
|
1388
|
+
|
|
1389
|
+
"""
|
|
1390
|
+
if page < 0 and log_type == "actions":
|
|
1391
|
+
url = f"{API_GET_LOG}?downloadActionsLog"
|
|
1392
|
+
else:
|
|
1393
|
+
url = f"{API_GET_LOG}?{log_type}&{page}"
|
|
1394
|
+
|
|
1395
|
+
resp = await self._api_request(
|
|
1396
|
+
"GET",
|
|
1397
|
+
url,
|
|
1398
|
+
priority=API_PRIORITY_NORMAL,
|
|
1399
|
+
)
|
|
1400
|
+
text = resp.strip() if resp else ""
|
|
1401
|
+
lines = text.split("\n") if text else []
|
|
1402
|
+
has_more = lines and lines[-1].strip() == "LOAD_MORE"
|
|
1403
|
+
if has_more:
|
|
1404
|
+
lines = lines[:-1]
|
|
1405
|
+
lines = [ln for ln in lines if ln.strip()]
|
|
1406
|
+
return {"lines": lines, "has_more": has_more, "raw": text}
|
|
1407
|
+
|
|
1408
|
+
async def get_notifications(self) -> dict[str, Any]:
|
|
1409
|
+
"""Fetch all notification history from the controller.
|
|
1410
|
+
|
|
1411
|
+
Returns:
|
|
1412
|
+
The JSON response dict where each key is a numeric ID and each
|
|
1413
|
+
value is a notification record with fields like DATE, TIME,
|
|
1414
|
+
SENSOR_ID, TYPE, TEXT, MAIL_STATE, etc.
|
|
1415
|
+
|
|
1416
|
+
"""
|
|
1417
|
+
return await self._api_request(
|
|
1418
|
+
"GET",
|
|
1419
|
+
f"{API_GET_NOTIFICATIONS}?ALL",
|
|
1420
|
+
priority=API_PRIORITY_NORMAL,
|
|
1421
|
+
)
|
|
@@ -31,8 +31,6 @@ from __future__ import annotations
|
|
|
31
31
|
|
|
32
32
|
API_READINGS = "/getReadings"
|
|
33
33
|
API_SET_FUNCTION_MANUALLY = "/setFunctionManually"
|
|
34
|
-
API_SET_DOSING_PARAMETERS = "/setDosingParameters"
|
|
35
|
-
API_SET_TARGET_VALUES = "/setTargetValues"
|
|
36
34
|
API_GET_CONFIG = "/getConfig"
|
|
37
35
|
API_SET_CONFIG = "/setConfig"
|
|
38
36
|
API_GET_CALIB_RAW_VALUES = "/getCalibRawValues"
|
|
@@ -44,6 +42,12 @@ API_GET_HISTORY = "/getHistory"
|
|
|
44
42
|
API_GET_WEATHER_DATA = "/getWeatherdata"
|
|
45
43
|
API_GET_OVERALL_DOSING = "/getOverallDosing"
|
|
46
44
|
API_GET_OUTPUT_STATES = "/getOutputstates"
|
|
45
|
+
API_GET_LOG = "/getLog"
|
|
46
|
+
API_GET_NOTIFICATIONS = "/getNotifications"
|
|
47
|
+
|
|
48
|
+
LOG_TYPE_ACTIONS = "actions"
|
|
49
|
+
LOG_TYPE_SWITCHING = "switching"
|
|
50
|
+
LOG_TYPE_ONEWIRE = "onewire"
|
|
47
51
|
|
|
48
52
|
# Settings for optimizing data refreshes by fetching specific groups.
|
|
49
53
|
SPECIFIC_READING_GROUPS = (
|
|
@@ -80,9 +84,9 @@ ACTION_UNLOCK = "UNLOCK"
|
|
|
80
84
|
|
|
81
85
|
# Common Query and Target Parameters
|
|
82
86
|
QUERY_ALL = "ALL"
|
|
83
|
-
TARGET_PH = "
|
|
84
|
-
TARGET_ORP = "
|
|
85
|
-
TARGET_MIN_CHLORINE = "
|
|
87
|
+
TARGET_PH = "DOSAGE_phminus_setpoint"
|
|
88
|
+
TARGET_ORP = "DOSAGE_chlorine_setpoint_orp"
|
|
89
|
+
TARGET_MIN_CHLORINE = "DOSAGE_chlorine_lowerval_cl"
|
|
86
90
|
KEY_MAINTENANCE = "MAINTENANCE"
|
|
87
91
|
KEY_PVSURPLUS = "PVSURPLUS"
|
|
88
92
|
|
|
@@ -161,6 +165,15 @@ DOSING_OUTPUT_INDEX = {
|
|
|
161
165
|
"DOS_6_FLOC": 5,
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
DOSING_CONFIG_PREFIX = {
|
|
169
|
+
"pH-": "DOSAGE_phminus",
|
|
170
|
+
"pH+": "DOSAGE_phplus",
|
|
171
|
+
"Chlor": "DOSAGE_chlorine",
|
|
172
|
+
"Elektrolyse": "DOSAGE_electrolysis",
|
|
173
|
+
"Flockmittel": "DOSAGE_floc",
|
|
174
|
+
"H2O2": "DOSAGE_h2o2",
|
|
175
|
+
}
|
|
176
|
+
|
|
164
177
|
# =============================================================================
|
|
165
178
|
# CONTROLLER ERROR CODES (Manual Section 27.2 - Software 1.1.9)
|
|
166
179
|
# =============================================================================
|
|
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
|
|
File without changes
|