violet-poolController-api 0.0.11__tar.gz → 0.0.12__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.11 → violet_poolcontroller_api-0.0.12}/PKG-INFO +1 -1
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/pyproject.toml +1 -1
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/setup.py +1 -1
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/tests/test_api.py +31 -5
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolController_api.egg-info/PKG-INFO +1 -1
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/api.py +34 -7
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/LICENSE +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/README.md +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/setup.cfg +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolController_api.egg-info/SOURCES.txt +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolController_api.egg-info/requires.txt +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolController_api.egg-info/top_level.txt +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/__init__.py +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/circuit_breaker.py +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/const_api.py +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/const_devices.py +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/utils_rate_limiter.py +0 -0
- {violet_poolcontroller_api-0.0.11 → violet_poolcontroller_api-0.0.12}/violet_poolcontroller_api/utils_sanitizer.py +0 -0
|
@@ -257,7 +257,7 @@ async def test_dosing_standalone_detection_dict_format(mock_aioresponse, standal
|
|
|
257
257
|
|
|
258
258
|
@pytest.mark.asyncio
|
|
259
259
|
async def test_get_hardware_profile(mock_aioresponse, api_client):
|
|
260
|
-
"""Test get_hardware_profile correctly detects components."""
|
|
260
|
+
"""Test get_hardware_profile correctly detects components via alive counters."""
|
|
261
261
|
url = "http://192.168.1.100/getReadings?ALL"
|
|
262
262
|
|
|
263
263
|
# 1. Base module only (no DOS, EXT)
|
|
@@ -270,8 +270,14 @@ async def test_get_hardware_profile(mock_aioresponse, api_client):
|
|
|
270
270
|
"extension_module_2": False,
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
# 2. Base module + Dosing + Ext1
|
|
274
|
-
mock_aioresponse.get(url, payload={"getReadings": {
|
|
273
|
+
# 2. Base module + Dosing + Ext1 (via alive counters)
|
|
274
|
+
mock_aioresponse.get(url, payload={"getReadings": {
|
|
275
|
+
"PUMPSTATE": "2",
|
|
276
|
+
"SYSTEM_dosagemodule_alive_count": "20392243",
|
|
277
|
+
"SYSTEM_dosagemodule_cpu_temperature": 45.5,
|
|
278
|
+
"SYSTEM_ext1module_alive_count": "52443888",
|
|
279
|
+
"EXT1_1": "1",
|
|
280
|
+
}}, status=200)
|
|
275
281
|
profile = await api_client.get_hardware_profile()
|
|
276
282
|
assert profile == {
|
|
277
283
|
"base_module": True,
|
|
@@ -280,8 +286,14 @@ async def test_get_hardware_profile(mock_aioresponse, api_client):
|
|
|
280
286
|
"extension_module_2": False,
|
|
281
287
|
}
|
|
282
288
|
|
|
283
|
-
# 3. Base module + Ext1 + Ext2 (no Dosing)
|
|
284
|
-
mock_aioresponse.get(url, payload={"getReadings": {
|
|
289
|
+
# 3. Base module + Ext1 + Ext2 (via alive counters, no Dosing)
|
|
290
|
+
mock_aioresponse.get(url, payload={"getReadings": {
|
|
291
|
+
"PUMPSTATE": "2",
|
|
292
|
+
"SYSTEM_ext1module_alive_count": "100",
|
|
293
|
+
"SYSTEM_ext2module_alive_count": "200",
|
|
294
|
+
"EXT1_1": "1",
|
|
295
|
+
"EXT2_1": "1",
|
|
296
|
+
}}, status=200)
|
|
285
297
|
profile = await api_client.get_hardware_profile()
|
|
286
298
|
assert profile == {
|
|
287
299
|
"base_module": True,
|
|
@@ -290,6 +302,20 @@ async def test_get_hardware_profile(mock_aioresponse, api_client):
|
|
|
290
302
|
"extension_module_2": True,
|
|
291
303
|
}
|
|
292
304
|
|
|
305
|
+
# 4. Real-world scenario: EXT2 relay data present (value 0) but no ext2 module
|
|
306
|
+
# Controller always returns EXT2_* keys even when the module is absent.
|
|
307
|
+
mock_aioresponse.get(url, payload={"getReadings": {
|
|
308
|
+
"PUMPSTATE": "2",
|
|
309
|
+
"SYSTEM_dosagemodule_alive_count": "20392243",
|
|
310
|
+
"SYSTEM_ext1module_alive_count": "52443888",
|
|
311
|
+
"EXT1_1": 0, "EXT1_2": 0,
|
|
312
|
+
"EXT2_1": 0, "EXT2_2": 0,
|
|
313
|
+
"EXT2_1_LAST_ON": 0, "EXT2_1_LAST_OFF": 0,
|
|
314
|
+
}}, status=200)
|
|
315
|
+
profile = await api_client.get_hardware_profile()
|
|
316
|
+
assert profile["extension_module_1"] is True
|
|
317
|
+
assert profile["extension_module_2"] is False
|
|
318
|
+
|
|
293
319
|
@pytest.mark.asyncio
|
|
294
320
|
async def test_get_hardware_profile_standalone_dosing(mock_aioresponse, standalone_api_client):
|
|
295
321
|
"""Test get_hardware_profile with a standalone dosing configuration."""
|
|
@@ -500,23 +500,50 @@ class VioletPoolAPI:
|
|
|
500
500
|
def _build_hardware_profile(self, flattened: dict[str, Any]) -> dict[str, bool]:
|
|
501
501
|
"""Build a hardware presence profile from flattened getReadings data.
|
|
502
502
|
|
|
503
|
+
Uses the controller's alive-counters (``SYSTEM_*_alive_count``) to
|
|
504
|
+
reliably detect connected hardware modules. The controller always
|
|
505
|
+
emits relay keys (``EXT1_1``, ``EXT2_1``, …) with a default value of
|
|
506
|
+
``0`` even when the physical module is absent, so checking those keys
|
|
507
|
+
would yield false positives.
|
|
508
|
+
|
|
503
509
|
Args:
|
|
504
510
|
flattened: The flattened key-value readings dict.
|
|
505
511
|
|
|
506
512
|
Returns:
|
|
507
513
|
A dictionary with boolean flags for connected hardware components.
|
|
508
514
|
"""
|
|
509
|
-
def
|
|
510
|
-
|
|
515
|
+
def _module_alive(alive_key: str) -> bool:
|
|
516
|
+
val = flattened.get(alive_key)
|
|
517
|
+
if val is None:
|
|
518
|
+
return False
|
|
519
|
+
try:
|
|
520
|
+
return float(str(val).strip()) > 0
|
|
521
|
+
except (ValueError, TypeError):
|
|
511
522
|
return False
|
|
512
|
-
|
|
513
|
-
|
|
523
|
+
|
|
524
|
+
def _any_relay_used(prefix: str) -> bool:
|
|
525
|
+
for key, val in flattened.items():
|
|
526
|
+
if not key.startswith(prefix):
|
|
527
|
+
continue
|
|
528
|
+
if "_LAST_ON" not in key and "_LAST_OFF" not in key and "_RUNTIME" not in key:
|
|
529
|
+
last_on = flattened.get(f"{key}_LAST_ON")
|
|
530
|
+
if last_on is not None:
|
|
531
|
+
try:
|
|
532
|
+
if float(str(last_on).strip()) > 0:
|
|
533
|
+
return True
|
|
534
|
+
except (ValueError, TypeError):
|
|
535
|
+
pass
|
|
536
|
+
return False
|
|
537
|
+
|
|
538
|
+
ext1 = _module_alive("SYSTEM_ext1module_alive_count") or _any_relay_used("EXT1_")
|
|
539
|
+
ext2 = _module_alive("SYSTEM_ext2module_alive_count") or _any_relay_used("EXT2_")
|
|
540
|
+
dosing = self.dosing_standalone or _module_alive("SYSTEM_dosagemodule_alive_count")
|
|
514
541
|
|
|
515
542
|
return {
|
|
516
543
|
"base_module": not self.dosing_standalone,
|
|
517
|
-
"dosing_module":
|
|
518
|
-
"extension_module_1":
|
|
519
|
-
"extension_module_2":
|
|
544
|
+
"dosing_module": dosing,
|
|
545
|
+
"extension_module_1": ext1,
|
|
546
|
+
"extension_module_2": ext2,
|
|
520
547
|
}
|
|
521
548
|
|
|
522
549
|
async def get_readings(self) -> dict[str, Any]:
|
|
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
|